MySQL::escape()   A
last analyzed

Complexity

Conditions 3
Paths 4

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 8
c 0
b 0
f 0
rs 9.4285
cc 3
eloc 4
nc 4
nop 2
1
<?php
2
namespace jrdev;
3
4
class MySQL extends \MySQLi
5
{
6
    private $dbConfig = array();
7
8
    private $connected = false;
9
10
    private $lastError = '';
11
12
    private $tables = [];
13
14
    public function __construct(
15
        $host = null,
16
        $username = null,
17
        $password = null,
18
        $dbname = null,
19
        $port = null,
20
        $socket = null
21
    ) {
22
        $this->dbConfig = [
23
            'host' => $host ?: ini_get("mysqli.default_host"),
24
            'username' => $username ?: ini_get("mysqli.default_user"),
25
            'password' => $password ?: ini_get("mysqli.default_pw"),
26
            'dbname' => $dbname ?: '',
27
            'port' => $port ?: ini_get("mysqli.default_port"),
28
            'socket' => $socket ?: ini_get("mysqli.default_socket")
29
        ];
30
    }
31
32
    public function connect(
0 ignored issues
show
Coding Style introduced by
function connect() does not seem to conform to the naming convention (^(?:is|has|should|may|supports)).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
33
        $host = null,
34
        $username = null,
35
        $password = null,
36
        $dbname = null,
37
        $port = null,
38
        $socket = null
39
    ) {
40
        if (true === $this->connected) {
41
            return true;
42
        }
43
44
        parent::__construct(
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class mysqli as the method __construct() does only exist in the following sub-classes of mysqli: jrdev\DB_MySQL, jrdev\MySQL. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
Comprehensibility Bug introduced by
It seems like you call parent on a different method (__construct() instead of connect()). Are you sure this is correct? If so, you might want to change this to $this->__construct().

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...
45
            $host ?: $this->dbConfig['host'],
46
            $username ?: $this->dbConfig['username'],
47
            $password ?: $this->dbConfig['password'],
48
            $dbname ?: $this->dbConfig['dbname'],
49
            $port ?: $this->dbConfig['port'],
50
            $socket ?: $this->dbConfig['socket']
51
        );
52
53
        if ($this->connect_error) {
54
            $this->error('MySQL Error: ' . $this->connect_errno . ' ' . $this->connect_error, true);
55
56
            return false;
57
        }
58
59
        // It's necessary for real_escape_string.
60
        if (false === $this->set_charset('utf8')) {
61
            $this->error('Error loading character set utf8: ' . $this->error);
62
63
            return false;
64
        }
65
66
        return $this->connected = true;
67
    }
68
69
    public function error($str = '', $fatal = false)
70
    {
71
        if ('' === $str) {
72
            return $this->lastError;
73
        } else {
74
            if (true === $fatal) {
75
                throw new \Exception($str);
76
            } else {
77
                $this->lastError = $str;
78
            }
79
        }
80
    }
81
82
    /**
83
     * Performs a generic query
84
     *
85
     * @param string $sql
86
     * @return MySQL\Result|false
87
     */
88
    public function query($sql, $resultMode = MYSQLI_STORE_RESULT)
89
    {
90
        if (false === $this->connect()) {
91
            return false;
92
        }
93
94
        switch ($resultMode) {
95
            case MYSQLI_USE_RESULT:
96
                $this->use_result();
97
                break;
98
            case MYSQLI_STORE_RESULT:
99
                $this->store_result();
100
                break;
101
        }
102
103
        if (false === $this->real_query($sql)) {
104
            $this->error('Error performing query ' . $sql . ' - Error message : ' . $this->error);
105
106
            return false;
107
        }
108
109
        return new MySQL\Result($this);
110
    }
111
112
    /**
113
     * Performs a INSERT statement
114
     *
115
     * @param string $tableName
116
     * @param array $fields
117
     * @return int Returns the ID of the inserted row, or false on error
118
     */
119
    public function insert($tableName, $fields)
120
    {
121
        $sql = "INSERT INTO `$tableName`"
122
        . ' (`' . implode('`,`', array_keys($fields)) . '`)'
123
        . ' VALUES ';
124
125
        $preparedFields = array();
126
127
        foreach ($fields as $fieldValue) {
128
            $preparedFields[] = $this->escape($fieldValue, true);
129
        }
130
131
        $sql .= '(' .implode(',', $preparedFields) . ')';
132
133
        if (false === $this->query($sql)) {
134
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type documented by jrdev\MySQL::insert of type integer.

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...
135
        } else {
136
            return $this->insert_id;
137
        }
138
    }
139
140
    public function escape($str, $quoted = false)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
141
    {
142
        $this->connect(); // It's necessary for real_escape_string.
143
144
        $result = $this->real_escape_string($str);
145
146
        return true === $quoted && preg_match('#^-?[0-9\.]+$#', $str) !== 1? "'{$result}'" : $result;
147
    }
148
149
    private function parseWhere($where)
150
    {
151
        if (is_array($where)) {
152
            $fields = array();
153
154
            foreach ($where as $fieldName => $fieldValue) {
155
                $fields[] = "`{$fieldName}` = " . $this->escape($fieldValue, true);
156
            }
157
158
            $whereSQL = implode(' AND ', $fields);
159
160
            $limit = null;
161
        } else {
162
            if (preg_match('#^-?[0-9]+$#', $where) === 1) {
163
                $whereSQL = "`id` = {$where}";
164
165
                $limit = 1;
166
            } else {
167
                $whereSQL = $where;
168
169
                $limit = null;
170
            }
171
        }
172
173
        return array($whereSQL, $limit);
174
    }
175
176
    /**
177
     * Performs an UPDATE statement
178
     *
179
     * @param string $tableName The name of the table
180
     * @param array $fields The fields to update
181
     * @param mixed $where Accepts array, string and integer
182
     * @param int $limit (Optional) The limit of rows to update
0 ignored issues
show
Documentation introduced by
Should the type for parameter $limit not be integer|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
183
     * @return int Returns the number of affected rows, or false on error
184
     */
185
    public function update($tableName, $fields, $where, $limit = null)
186
    {
187
        $sql = "UPDATE `{$tableName}` SET ";
188
189
        $preparedFields = array();
190
191
        foreach ($fields as $fieldName => $fieldValue) {
192
            $preparedFields[] = "`$fieldName` = " . $this->escape($fieldValue, true);
193
        }
194
195
        $sql .= implode(',', $preparedFields);
196
197
        list($pWhere, $pLimit) = $this->parseWhere($where);
198
199
        $where = $pWhere;
200
201
        $sql .= " WHERE {$where}";
202
203
        if (null === $limit && null !== $pLimit) {
204
            $limit = $pLimit;
205
        }
206
207
        if (null !== $limit) {
208
            $sql .= " LIMIT {$limit}";
209
        }
210
211
        if (false === $this->query($sql)) {
212
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type documented by jrdev\MySQL::update of type integer.

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...
213
        } else {
214
            return $this->affected_rows;
215
        }
216
    }
217
218
    /**
219
     * Performs a DELETE statement
220
     *
221
     * @param string $tableName The name of the table
222
     * @param string $where The where
223
     * @param int $limit (Optional) The limit
0 ignored issues
show
Documentation introduced by
Should the type for parameter $limit not be integer|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
224
     * @return int Returns the number of affected rows, or false on error
225
     */
226
    public function delete($tableName, $where, $limit = null)
227
    {
228
        $sql = "DELETE FROM `{$tableName}`";
229
230
        list($pWhere, $pLimit) = $this->parseWhere($where);
231
232
        $where = $pWhere;
233
234
        $sql .= " WHERE {$where}";
235
236
        if (null === $limit && null !== $pLimit) {
237
            $limit = $pLimit;
238
        }
239
240
        if (null !== $limit) {
241
            $sql .= " LIMIT {$limit}";
242
        }
243
244
        if (false === $this->query($sql)) {
245
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type documented by jrdev\MySQL::delete of type integer.

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...
246
        } else {
247
            return $this->affected_rows;
248
        }
249
    }
250
251
    /**
252
     * Performs a SELECT statement
253
     *
254
     * @param string $tableName The name of the table
255
     * @param mixed $fields (Optional) The fields you want to obtain in the result. Accepts array or string
256
     * @param mixed $where (Optional) The where. Accepts array, string or intenger
257
     * @param string $orderBy (Optional) The order by
0 ignored issues
show
Documentation introduced by
Should the type for parameter $orderBy not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
258
     * @param int $limit (Optional) The limit
0 ignored issues
show
Documentation introduced by
Should the type for parameter $limit not be integer|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
259
     * @return MySQL_Result
0 ignored issues
show
Documentation introduced by
Should the return type not be false|MySQL\Result?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
260
     */
261
    public function select($tableName, $fields = null, $where = null, $orderBy = null, $limit = null)
262
    {
263
        if (is_array($fields)) {
264
            foreach ($fields as $key => $value) {
265
                $fields[$key] = "`{$value}`";
266
            }
267
268
            $fields = implode(',', $fields);
269
        } elseif (is_null($fields)) {
270
            $fields = '*';
271
        }
272
273
        $sql = "SELECT {$fields} FROM `{$tableName}`";
274
275
        if (!is_null($where)) {
276
            list($pWhere, $pLimit) = $this->parseWhere($where);
277
278
            $where = $pWhere;
279
280
            if (null === $limit && null !== $pLimit) {
281
                $limit = $pLimit;
282
            }
283
284
            $sql .= " WHERE {$where}";
285
        }
286
287
        if (!is_null($orderBy)) {
288
            $sql .= " ORDER BY {$orderBy}";
289
        }
290
291
        if (!is_null($limit)) {
292
            $sql .= " LIMIT {$limit}";
293
        }
294
295
        return $this->query($sql);
296
    }
297
298
    public function table($tableName, $tableArgs = [])
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
299
    {
300
        if (! isset($this->tables[$tableName])) {
301
            $this->tables[$tableName] = new MySQL_Table($this, $tableName, $tableArgs);
302
        }
303
304
        return $this->tables[$tableName];
305
    }
306
307
    /**
308
     * Close the connection when instance is destroyed.
309
     */
310
    public function __destruct()
311
    {
312
        if (false === $this->connected) {
313
            return;
314
        }
315
316
        $this->close();
317
    }
318
}
319