Completed
Push — work-fleets ( 0059e8...71ccb1 )
by SuperNova.WS
06:08
created

DbSqlPrepare   A

Complexity

Total Complexity 29

Size/Duplication

Total Lines 283
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Test Coverage

Coverage 6.67%

Importance

Changes 6
Bugs 0 Features 0
Metric Value
c 6
b 0
f 0
dl 0
loc 283
ccs 6
cts 90
cp 0.0667
rs 10
wmc 29
lcom 1
cbo 5

15 Methods

Rating   Name   Duplication   Size   Complexity  
A buildIterator() 0 3 1
A storeResult() 0 5 1
A __construct() 0 9 1
A build() 0 3 1
A setQuery() 0 5 1
A commentRemove() 0 6 2
A commentAdd() 0 6 2
A comment() 0 9 2
C compileMySqlI() 0 49 7
A bindParams() 0 7 2
A statementGet() 0 21 3
A execute() 0 5 1
A getResult() 0 3 1
A getIterator() 0 15 3
A __toString() 0 6 1
1
<?php
2
3
/**
4
 * Class DbSqlPrepare
5
 */
6
// TODO - Вааще всё переделать.
7
// Получение стейтмента по строке - это должен быть статик
8
// Тогда же должны ребиндится параметры
9
10
// TODO - Переключатель - использовать get_result или нет
11
class DbSqlPrepare {
12
  const COMMENT_PLACEHOLDER = ':__COMMENT__';
13
  const DDL_REGEXP = '#(\|.+?\b)#im';
14
  const PARAM_REGEXP = '#(\:.+?\b)#im';
15
16
  /**
17
   * SQL text
18
   *
19
   * @var string|DbQueryConstructor
20
   */
21
  public $query = '';
22
  /**
23
   * Array of used params :param => value
24
   * Each param would bind with bind_param() function
25
   * Used for DML part of query
26
   *
27
   * @var array $values
28
   */
29
  public $values = array();
30
  /**
31
   * Array of used placeholders |param => value
32
   * Each placeholder would be placed into query with conversion via (string)
33
   * Used for dynamic queries creation for DDL part
34
   *
35
   * @var array $placeholders
36
   */
37
  // TODO - make use of it
38
  public $placeholders = array();
39
40
  /**
41
   * Comment for query
42
   * Should be quoted with SQL comment quote
43
   * Will be placed after query itself. Used mainly for debug purposes (should be disabled on production servers)
44
   *
45
   * @var string
46
   */
47
  // TODO - disable comments in SQL via game config
48
  public $comment = '';
49
50
  // Prepared values for query execution
51
  public $queryPrepared = '';
52
  public $paramsPrepared = array();
53
  public $valuesPrepared = array();
54
  public $valueTypesPrepared = '';
55
  /**
56
   * @var mysqli_stmt $statement
57
   */
58
  public $statement;
59
  /**
60
   * Flag that params already bound
61
   * Setting values performed via updating $values property
62
   *
63
   * @var bool $isParamsBound
64
   */
65
  protected $isParamsBound = false;
66
67
//  /**
68
//   * @var ReflectionMethod
69
//   */
70
//  public static $bindParamMethod;
71
72
  /**
73
   * @var mysqli_stmt[]
74
   */
75
  protected static $statements = array();
76
77
  public static $isUseGetResult = false;
78
79
  /**
80
   * DbSqlPrepare constructor.
81
   *
82
   * @param string $query
83
   * @param array  $values
84
   */
85 1
  public function __construct($query, $values = array()) {
86
//    if(empty(static::$bindParamMethod)) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
61% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
87
//      $ref = new ReflectionClass('mysqli_stmt');
88
//      static::$bindParamMethod = $ref->getMethod("bind_param");
89
//    }
90
91 1
    $this->query = trim($query);
92 1
    $this->values = $values;
93 1
  }
94
95
  /**
96
   * @param string $query
97
   * @param array  $values
98
   *
99
   * @return static
100
   *
101
   */
102 1
  public static function build($query, $values = array()) {
103 1
    return new static($query, $values);
104
  }
105
106
  /**
107
   * @param string $query
108
   * @param array  $values
109
   *
110
   * @return static
111
   *
112
   */
113
  public static function buildIterator($query, $values = array()) {
114
    return static::build($query, $values);
115
  }
116
117
118
  public function setQuery($query) {
119
    $this->query = $query;
120
121
    return $this;
122
  }
123
124
  protected function commentRemove() {
125
    if (!empty($this->values[static::COMMENT_PLACEHOLDER])) {
126
      unset($this->values[static::COMMENT_PLACEHOLDER]);
127
      $this->query = str_replace(static::COMMENT_PLACEHOLDER, '', $this->query);
128
    }
129
  }
130
131
  protected function commentAdd($comment) {
132
    if (empty($this->values[static::COMMENT_PLACEHOLDER])) {
133
      $this->query .= static::COMMENT_PLACEHOLDER;
134
    }
135
    $this->values[static::COMMENT_PLACEHOLDER] = $comment;
136
  }
137
138
  /**
139
   * @param string $comment
140
   */
141
  public function comment($comment) {
142
    if (empty($comment)) {
143
      $this->commentRemove();
144
    } else {
145
      $this->commentAdd($comment);
146
    }
147
148
    return $this;
149
  }
150
151
152
  // TODO - method to re-set and re-bind values
153
154
155
  public function compileMySqlI() {
156
    $this->queryPrepared = $this->query;
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->query can also be of type object<DbQueryConstructor>. However, the property $queryPrepared is declared as type string. Maybe add an additional type check?

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 the id property of an instance of the Account 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.

class Id
{
    public $id;

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

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
157
    $this->paramsPrepared = array();
158
    $this->valuesPrepared = array();
159
    $this->valueTypesPrepared = '';
160
161
    if ($variableCount = preg_match_all(self::PARAM_REGEXP, $this->query, $matches, PREG_PATTERN_ORDER)) {
0 ignored issues
show
Unused Code introduced by
$variableCount is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
162
      $this->paramsPrepared = $matches[0];
163
      if (in_array(static::COMMENT_PLACEHOLDER, $this->paramsPrepared)) {
164
        // Removing comment placeholder from statement
165
        $this->queryPrepared = str_replace(static::COMMENT_PLACEHOLDER, DbSqlHelper::quoteComment($this->comment), $this->queryPrepared);
166
        // Removing comment value from values list
167
        $this->paramsPrepared = array_filter($this->paramsPrepared, function ($value) { return $value != DbSqlPrepare::COMMENT_PLACEHOLDER; });
168
        // TODO - Add comment value directly to statement
169
      }
170
171
      // Replacing actual param names with '?' - for mysqli_prepare
172
      $this->queryPrepared = preg_replace(self::PARAM_REGEXP, '?', $this->queryPrepared);
0 ignored issues
show
Documentation Bug introduced by
It seems like preg_replace(self::PARAM..., $this->queryPrepared) can also be of type array<integer,string>. However, the property $queryPrepared is declared as type string. Maybe add an additional type check?

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 the id property of an instance of the Account 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.

class Id
{
    public $id;

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

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
173
174
      // Now filling found params with it values
175
      // We can't use param names as keys 'cause same param can be met twice
176
      // So one key would overwrite another and total number of valuesUsed will be less then actual values used
177
      // TODO - move out of this proc to separate method to allow rebind of params
178
      foreach ($this->paramsPrepared as $key => &$value) {
179
        if (!key_exists($value, $this->values)) {
180
          // Throw exception if not key found in statement values list
181
          throw new Exception('DbSqlPrepare::compileMySqlI() - values array has no match for statement params');
182
        }
183
184
        // Reference need for call mysqli::bind_param later in bindParam() method
185
        $this->valuesPrepared[$key] = &$this->values[$value];
186
187
        // TODO - move out of this proc to separate method and own loop to allow rebind of params
188
        // i corresponding variable has type integer
189
        // d corresponding variable has type double
190
        // s corresponding variable has type string
191
        // b corresponding variable is a blob and will be sent in packets
192
        if (is_int($this->values[$value])) {
193
          $this->valueTypesPrepared .= 'i';
194
        } elseif (is_double($this->values[$value])) {
195
          $this->valueTypesPrepared .= 'd';
196
        } else {
197
          $this->valueTypesPrepared .= 's';
198
        }
199
      }
200
    }
201
202
    return $this;
203
  }
204
205
206
  /**
207
   * @internal param mysqli_stmt $mysqli_stmt
208
   */
209
  protected function bindParams() {
210
    if (count($this->valuesPrepared)) {
211
      $params = array_merge(array(&$this->valueTypesPrepared), $this->valuesPrepared);
212
      // static::$bindParamMethod->invokeArgs($this->statement, $params);
0 ignored issues
show
Unused Code Comprehensibility introduced by
74% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
213
      call_user_func_array(array($this->statement, 'bind_param'), $params);
214
    }
215
  }
216
217
  /**
218
   * @param db_mysql $db
219
   *
220
   * @return DbSqlPrepare
221
   * @throws Exception
222
   */
223
  public function statementGet($db) {
224
    // TODO - к этому моменту плейсхолдеры под DDL уже должны быть заполнены соответствующими значениями
225
    // Надо вынести собственно prepared statement в отдельный объект, что бы здесь остались только манипуляции с полями
226
    $md5 = md5($this->queryPrepared);
227
228
    if (empty(static::$statements[$md5])) {
229
      if (!(static::$statements[$md5] = $db->db_prepare($this->queryPrepared))) {
230
        throw new Exception('DbSqlPrepare::statementGet() - can not prepare statement');
231
      }
232
//      $this->statement = static::$statements[$md5];
0 ignored issues
show
Unused Code Comprehensibility introduced by
55% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
233
//      $this->bindParams();
234
//    } else {
235
//      // TODO - вот тут фигня. На самом деле нельзя под один и тот же DbSqlPrepare исползовать разные mysqli_stmt
236
//      // С другой стороны - это позволяет реюзать параметры. Так что еще вопрос - фигня ли это....
237
//      $this->statement = static::$statements[$md5];
238
    }
239
    $this->statement = static::$statements[$md5];
0 ignored issues
show
Documentation Bug introduced by
It seems like static::$statements[$md5] can also be of type boolean. However, the property $statement is declared as type object<mysqli_stmt>. Maybe add an additional type check?

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 the id property of an instance of the Account 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.

class Id
{
    public $id;

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

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
240
    $this->bindParams();
241
242
    return $this;
243
  }
244
245
  /**
246
   * @return $this
247
   */
248
  public function execute() {
249
    $this->statement->execute();
250
251
    return $this;
252
  }
253
254
  /**
255
   * @return $this
256
   */
257
  public function storeResult() {
258
    $this->statement->store_result();
259
260
    return $this;
261
  }
262
263
  /**
264
   * @return bool|mysqli_result
265
   */
266
  public function getResult() {
267
    return $this->statement->get_result();
268
  }
269
270
  public function getIterator() {
271
    if(DbSqlPrepare::$isUseGetResult) {
272
      $mysqli_result = $this->statement->get_result();
273
      if($mysqli_result instanceof mysqli_result) {
274
        $iterator = new DbMysqliResultIterator($this->statement->get_result());
275
      } else {
276
        $iterator = new DbEmptyIterator();
277
      }
278
    } else {
279
      $this->storeResult();
280
      $iterator = new DBMysqliStatementIterator($this->statement);
281
    }
282
283
    return $iterator;
284
  }
285
286
  public function __toString() {
287
//    $result = str_replace(array_keys($this->tables), $this->tables, $this->query);
0 ignored issues
show
Unused Code Comprehensibility introduced by
57% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
288
    $result = str_replace(array_keys($this->values), $this->values, $this->query);
289
290
    return $result;
291
  }
292
293
}
294
295
DbSqlPrepare::$isUseGetResult = method_exists('mysqli_stmt', 'get_result');
296