Worker   C
last analyzed

Complexity

Total Complexity 57

Size/Duplication

Total Lines 302
Duplicated Lines 29.47 %

Coupling/Cohesion

Components 1
Dependencies 1

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
wmc 57
lcom 1
cbo 1
dl 89
loc 302
ccs 0
cts 236
cp 0
rs 5.04
c 0
b 0
f 0

9 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
B updateWorkers() 0 28 10
A getAllIdleWorkers() 35 35 5
B getWorker() 0 39 7
B getWorkers() 13 43 7
B getAllWorkers() 13 44 7
A getCountAllActiveWorkers() 12 12 6
B addWorker() 0 26 9
A deleteWorker() 7 7 5

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Worker often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Worker, and based on these observations, apply Extract Interface, too.

1
<?php
2
$defflip = (!cfip()) ? exit(header('HTTP/1.1 401 Unauthorized')) : 1;
3
4
class Worker extends Base {
5
  protected $table = 'pool_worker';
6
7
  /**
8
   * We allow changing the database for shared accounts across pools
9
   * Load the config on construct so we can assign the DB name
10
   * @param config array MPOS configuration
11
   * @return none
0 ignored issues
show
Comprehensibility Best Practice introduced by
Adding a @return annotation to constructors is generally not recommended as a constructor does not have a meaningful return value.

Adding a @return annotation to a constructor is not recommended, since a constructor does not have a meaningful return value.

Please refer to the PHP core documentation on constructors.

Loading history...
12
   **/
13
  public function __construct($config) {
14
    $this->setConfig($config);
15
    $this->table = $this->config['db']['shared']['workers'] . '.' . $this->table;
0 ignored issues
show
Bug introduced by
The property config does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
16
  }
17
18
  /**
19
   * Update worker list for a user
20
   * @param account_id int User ID
21
   * @param data array All workers and their settings
22
   * @return bool
23
   **/
24
  public function updateWorkers($account_id, $data) {
25
    $this->debug->append("STA " . __METHOD__, 4);
26
    if (!is_array($data)) {
27
      $this->setErrorMessage('No workers to update');
28
      return false;
29
    }
30
    $username = $this->user->getUserName($account_id);
0 ignored issues
show
Bug introduced by
The property user does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
31
    $iFailed = 0;
32
    foreach ($data as $key => $value) {
33
    if ('' === $value['username'] || '' === $value['password']) {
34
      $iFailed++;
35
    } else {
36
      // Check worker name first
37
      if (! preg_match("/^[0-9a-zA-Z_\-]*$/", $value['username'])) {
38
        $iFailed++;
39
        continue;
40
      }
41
      // Prefix the WebUser to Worker name
42
      $value['username'] = "$username." . $value['username'];
43
      $stmt = $this->mysqli->prepare("UPDATE $this->table SET password = ?, username = ?, monitor = ? WHERE account_id = ? AND id = ? LIMIT 1");
44
      if ( ! ( $this->checkStmt($stmt) && $stmt->bind_param('ssiii', $value['password'], $value['username'], $value['monitor'], $account_id, $key) && $stmt->execute()) )
45
        $iFailed++;
46
      }
47
    }
48
    if ($iFailed == 0)
49
      return true;
50
    return $this->sqlError('E0053', $iFailed);
51
  }
52
53
  /**
54
   * Fetch all IDLE workers that have monitoring enabled
55
   * @param none
56
   * @return data array Workers in IDLE state and monitoring enabled
57
   **/
58 View Code Duplication
  public function getAllIdleWorkers($interval=600) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
59
    $this->debug->append("STA " . __METHOD__, 4);
60
    $stmt = $this->mysqli->prepare("
61
      SELECT w.account_id AS account_id, w.id AS id, w.username AS username
62
      FROM
63
      (
64
        SELECT username AS s_username, MAX(shares_id) AS shares_id, MAX(shares_archive_id) AS shares_archive_id
65
        FROM
66
        (
67
          SELECT
68
          s.username AS username, MAX(s.id) AS shares_id, NULL AS shares_archive_id
69
          FROM . " . $this->share->getTableName() . " AS s
0 ignored issues
show
Bug introduced by
The property share does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
70
          WHERE s.time > DATE_SUB(now(), INTERVAL ? SECOND)
71
          AND s.our_result = 'Y'
72
          GROUP BY s.username
73
          UNION
74
          SELECT
75
          sa.username AS username, NULL AS shares_id, MAX(sa.id) AS shares_archive_id
76
          FROM " . $this->share->getArchiveTableName() . " AS sa
77
          WHERE sa.time > DATE_SUB(now(), INTERVAL ? SECOND)
78
          AND sa.our_result = 'Y'
79
          GROUP BY sa.username
80
        ) AS derived0
81
        GROUP BY s_username
82
      ) AS derived1
83
      RIGHT JOIN " . $this->getTableName() . " AS w
84
      ON s_username = w.username
85
      WHERE w.monitor = 1
86
      AND shares_id IS NULL
87
      AND shares_archive_id IS NULL
88
    ");
89
    if ($this->checkStmt($stmt) && $stmt->bind_param('ii', $interval, $interval) && $stmt->execute() && $result = $stmt->get_result())
90
      return $result->fetch_all(MYSQLI_ASSOC);
91
    return $this->sqlError('E0054');
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->sqlError('E0054'); (boolean) is incompatible with the return type documented by Worker::getAllIdleWorkers of type data.

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...
92
  }
93
94
  /**
95
   * Fetch a specific worker and its status
96
   * @param id int Worker ID
97
   * @return mixed array Worker details
98
   **/
99
  public function getWorker($id, $interval=600) {
100
    $this->debug->append("STA " . __METHOD__, 4);
101
    $stmt = $this->mysqli->prepare("
102
      SELECT id, username, password, monitor,
103
        (
104
          SELECT COUNT(id) FROM " . $this->share->getTableName() . " WHERE our_result = 'Y' AND username = w.username AND time > DATE_SUB(now(), INTERVAL ? SECOND)
105
        ) + (
106
          SELECT COUNT(id) FROM " . $this->share->getArchiveTableName() . " WHERE our_result = 'Y' AND username = w.username AND time > DATE_SUB(now(), INTERVAL ? SECOND)
107
        ) AS count_all,
108
        (
109
          SELECT
110
          IFNULL(SUM(difficulty), 0)
111
          FROM " . $this->share->getTableName() . "
112
          WHERE
113
            username = w.username
114
            AND our_result = 'Y'
115
            AND time > DATE_SUB(now(), INTERVAL ? SECOND)
116
        ) + (
117
          SELECT
118
          IFNULL(SUM(difficulty), 0)
119
          FROM " . $this->share->getArchiveTableName() . "
120
          WHERE
121
            username = w.username
122
            AND our_result = 'Y'
123
            AND time > DATE_SUB(now(), INTERVAL ? SECOND)
124
         ) AS shares
125
       FROM $this->table AS w
126
       WHERE id = ?");
127
    if ($this->checkStmt($stmt) && $stmt->bind_param('iiiii', $interval, $interval, $interval, $interval, $id) && $stmt->execute() && ($result = $stmt->get_result()) && ($row = $result->fetch_assoc())) {
128
      $row['hashrate'] = round($this->coin->calcHashrate($row['shares'], $interval), 2);
0 ignored issues
show
Bug introduced by
The property coin does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
129
      if ($row['count_all'] > 0) {
130
        $row['difficulty'] = round($row['shares'] / $row['count_all'], 2);
131
      } else {
132
        $row['difficulty'] = 0.00;
133
      }
134
      return $row;
135
    }
136
    return $this->sqlError('E0055');
137
  }
138
139
  /**
140
   * Fetch all workers for an account
141
   * @param account_id int User ID
142
   * @return mixed array Workers and their settings or false
143
   **/
144
  public function getWorkers($account_id, $interval=600) {
145
    $this->debug->append("STA " . __METHOD__, 4);
146
    $stmt = $this->mysqli->prepare("
147
      SELECT id, username, password, monitor,
148
        (
149
          SELECT COUNT(id) FROM " . $this->share->getTableName() . " WHERE our_result = 'Y' AND username = w.username AND time > DATE_SUB(now(), INTERVAL ? SECOND)
150
        ) + (
151
          SELECT COUNT(id) FROM " . $this->share->getArchiveTableName() . " WHERE our_result = 'Y' AND username = w.username AND time > DATE_SUB(now(), INTERVAL ? SECOND)
152
        ) AS count_all,
153
        (
154
          SELECT
155
          IFNULL(SUM(difficulty), 0)
156
          FROM " . $this->share->getTableName() . "
157
          WHERE
158
            username = w.username
159
            AND our_result = 'Y'
160
            AND time > DATE_SUB(now(), INTERVAL ? SECOND)
161
        ) + (
162
          SELECT
163
          IFNULL(SUM(difficulty), 0)
164
          FROM " . $this->share->getArchiveTableName() . "
165
          WHERE
166
            username = w.username
167
            AND our_result = 'Y'
168
            AND time > DATE_SUB(now(), INTERVAL ? SECOND)
169
        ) AS shares
170
      FROM $this->table AS w
171
      WHERE account_id = ?");
172 View Code Duplication
    if ($this->checkStmt($stmt) && $stmt->bind_param('iiiii', $interval, $interval, $interval, $interval, $account_id) && $stmt->execute() && $result = $stmt->get_result()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
173
      $aData = array();
174
      while ($row = $result->fetch_assoc()) {
175
        $row['hashrate'] = round($this->coin->calcHashrate($row['shares'], $interval), 2);
176
        if ($row['count_all'] > 0) {
177
          $row['difficulty'] = round($row['shares'] / $row['count_all'], $this->coin->getShareDifficultyPrecision());
178
        } else {
179
          $row['difficulty'] = 0.00;
180
        }
181
        $aData[] = $row;
182
      }
183
      return $aData;
184
    }
185
    return $this->sqlError('E0056');
186
  }
187
188
  /**
189
   * Fetch all workers for admin panel
190
   * @param limit int max amount of workers
191
   * @return mixed array Workers and their settings or false
192
   **/
193
  public function getAllWorkers($iLimit=0, $interval=600, $start=0) {
194
    $this->debug->append("STA " . __METHOD__, 4);
195
    $stmt = $this->mysqli->prepare("
196
      SELECT id, username, password, monitor,
197
        (
198
          SELECT COUNT(id) FROM " . $this->share->getTableName() . " WHERE our_result = 'Y' AND username = w.username AND time > DATE_SUB(now(), INTERVAL ? SECOND)
199
        ) + (
200
          SELECT COUNT(id) FROM " . $this->share->getArchiveTableName() . " WHERE our_result = 'Y' AND username = w.username AND time > DATE_SUB(now(), INTERVAL ? SECOND)
201
        ) AS count_all,
202
        IFNULL(IF(difficulty=0, pow(2, (" . $this->config['difficulty'] . " - 16)), difficulty), 0) AS difficulty,
203
        (
204
          SELECT
205
          IFNULL(SUM(difficulty), 0)
206
          FROM " . $this->share->getTableName() . "
207
          WHERE
208
            username = w.username
209
            AND our_result = 'Y'
210
            AND time > DATE_SUB(now(), INTERVAL ? SECOND)
211
        ) + (
212
          SELECT
213
          IFNULL(SUM(difficulty), 0)
214
          FROM " . $this->share->getArchiveTableName() . "
215
          WHERE
216
            username = w.username
217
            AND our_result = 'Y'
218
            AND time > DATE_SUB(now(), INTERVAL ? SECOND)
219
        ) AS shares
220
      FROM $this->table AS w
221
      ORDER BY shares DESC LIMIT ?,?");
222 View Code Duplication
    if ($this->checkStmt($stmt) && $stmt->bind_param('iiiiii', $interval, $interval, $interval, $interval, $start, $iLimit) && $stmt->execute() && $result = $stmt->get_result()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
223
      $aData = array();
224
      while ($row = $result->fetch_assoc()) {
225
        $row['hashrate'] = round($this->coin->calcHashrate($row['shares'], $interval), 2);
226
        if ($row['count_all'] > 0) {
227
          $row['avg_difficulty'] = round($row['shares'] / $row['count_all'], 2);
228
        } else {
229
          $row['avg_difficulty'] = 0.00;
230
        }
231
        $aData[] = $row;
232
      }
233
      return $aData;
234
    }
235
    return $this->sqlError('E0057');
236
  }
237
238
  /**
239
   * Get all currently active workers in the past 2 minutes
240
   * @param none
241
   * @return data mixed int count if any workers are active, false otherwise
242
   **/
243 View Code Duplication
  public function getCountAllActiveWorkers($interval=120) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
244
    $this->debug->append("STA " . __METHOD__, 4);
245
    if ($data = $this->memcache->get(__FUNCTION__)) return $data;
0 ignored issues
show
Bug introduced by
The property memcache does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
246
    $stmt = $this->mysqli->prepare("
247
      SELECT COUNT(DISTINCT(username)) AS total
248
      FROM "  . $this->share->getTableName() . "
249
      WHERE our_result = 'Y'
250
      AND time > DATE_SUB(now(), INTERVAL ? SECOND)");
251
    if ($this->checkStmt($stmt) && $stmt->bind_param('i', $interval) && $stmt->execute() && $result = $stmt->get_result())
252
      return $this->memcache->setCache(__FUNCTION__, $result->fetch_object()->total);
253
    return $this->sqlError();
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->sqlError(); (boolean) is incompatible with the return type documented by Worker::getCountAllActiveWorkers of type data.

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...
254
  }
255
256
  /**
257
   * Add new worker to an existing web account
258
   * The webuser name is prefixed to the worker name
259
   * Passwords are plain text for pushpoold
260
   * @param account_id int User ID
261
   * @param workerName string Worker name
262
   * @param workerPassword string Worker password
263
   * @return bool
264
   **/
265
  public function addWorker($account_id, $workerName, $workerPassword) {
266
    $this->debug->append("STA " . __METHOD__, 4);
267
    if ('' === $workerName || '' === $workerPassword) {
268
      $this->setErrorMessage($this->getErrorMsg('E0058'));
269
      return false;
270
    }
271
    if (!preg_match("/^[0-9a-zA-Z_\-]*$/", $workerName)) {
272
      $this->setErrorMessage($this->getErrorMsg('E0072'));
273
      return false;
274
    }
275
    $username = $this->user->getUserName($account_id);
276
    $workerName = "$username.$workerName";
277
    if (strlen($workerName) > 50) {
278
      $this->setErrorMessage($this->getErrorMsg('E0073'));
279
      return false;
280
    }
281
    $stmt = $this->mysqli->prepare("INSERT INTO $this->table (account_id, username, password) VALUES(?, ?, ?)");
282
    if ($this->checkStmt($stmt) && $stmt->bind_param('iss', $account_id, $workerName, $workerPassword)) {
283
      if (!$stmt->execute()) {
284
        if ($stmt->sqlstate == '23000') return $this->sqlError('E0059');
285
      } else {
286
        return true;
287
      }
288
    }
289
    return $this->sqlError('E0060');
290
  }
291
292
  /**
293
   * Delete existing worker from account
294
   * @param account_id int User ID
295
   * @param id int Worker ID
296
   * @return bool
297
   **/
298 View Code Duplication
  public function deleteWorker($account_id, $id) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
299
    $this->debug->append("STA " . __METHOD__, 4);
300
    $stmt = $this->mysqli->prepare("DELETE FROM $this->table WHERE account_id = ? AND id = ? LIMIT 1");
301
    if ($this->checkStmt($stmt) && $stmt->bind_param('ii', $account_id, $id) && $stmt->execute() && $stmt->affected_rows == 1)
302
        return true;
303
    return $this->sqlError('E0061');
304
  }
305
}
306
307
$worker = new Worker($config);
308
$worker->setDebug($debug);
309
$worker->setMysql($mysqli);
310
$worker->setMemcache($memcache);
311
$worker->setShare($share);
312
$worker->setUser($user);
313
$worker->setErrorCodes($aErrorCodes);
314
$worker->setCoin($coin);
315