Records   A
last analyzed

Complexity

Total Complexity 41

Size/Duplication

Total Lines 125
Duplicated Lines 0 %

Importance

Changes 4
Bugs 0 Features 0
Metric Value
eloc 90
c 4
b 0
f 0
dl 0
loc 125
rs 9.1199
wmc 41

6 Methods

Rating   Name   Duplication   Size   Complexity  
A getMemo() 0 3 2
A getDateTime() 0 11 3
A checkNullColumn() 0 3 2
A convertChar() 0 2 1
D nextRecord() 0 63 27
A __construct() 0 19 6

How to fix   Complexity   

Complex Class

Complex classes like Records 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.

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 Records, and based on these observations, apply Extract Interface, too.

1
<?php
2
/********************************************
3
 * DBF-file records Reader
4
 *
5
 * Author: Chizhov Nikolay <[email protected]>
6
 * (c) 2019-2024 CIOB "Inok"
7
 ********************************************/
8
9
namespace Inok\Dbf;
10
11
use Exception;
12
13
class Records {
14
  private $fp, $headers, $columns, $memo, $encode;
15
  private $records = 0;
16
17
  private $v_fox_versions = [48, 49, 50];
18
  private $v_fox = false;
19
  private $nullFlagColumns = [];
20
  private $logicals = ['t', 'y', 'д'];
21
  private $notTrimTypes = ["M", "P", "G", "I", "Y", "T", "0"];
22
23
  /**
24
   * @throws Exception
25
   */
26
  public function __construct($data, $encode = "utf-8", $headers = null, $columns = null) {
27
    if ($data instanceof Table) {
28
      $this->headers = $data->getHeaders();
29
      $this->columns = $data->getColumns();
30
      $this->fp = $data->getData();
31
    }
32
    else {
33
      if (is_null($headers) || is_null($columns)) {
34
        throw new Exception('Not correct data in Record class');
35
      }
36
      $this->fp = $data;
37
      $this->headers = $headers;
38
      $this->columns = $columns;
39
    }
40
    $this->encode = $encode;
41
    if ($this->headers["memo"] && !is_null($this->headers["memo_file"])) {
42
      $this->memo = new Memo($this->headers["memo_file"]);
43
    }
44
    $this->v_fox = in_array($this->headers["version"], $this->v_fox_versions);
45
  }
46
47
  public function nextRecord() {
48
    if ($this->records >= $this->headers["records"]) {
49
      if ($this->memo instanceof Memo) {
50
        unset($this->memo);
51
      }
52
      return false;
53
    }
54
    $record = [];
55
    $data = fread($this->fp, $this->headers["record_length"]);
56
    $record["deleted"] = (unpack("C", $data[0])[1] == 42);
57
    $pos = 1;
58
    foreach ($this->columns as $column) {
59
      $sub_data = (in_array($column["type"], $this->notTrimTypes)) ? substr($data, $pos, $column["length"]) : trim(substr($data, $pos, $column["length"]));
60
      switch($column["type"]) {
61
        case "F":
62
        case "N":
63
          $record[$column["name"]] = (is_numeric($sub_data)) ? (($column["decimal"]) ? (float) $sub_data : (int) $sub_data) : null;
64
          break;
65
        case "Y":
66
          $decimal = intval(str_pad("1", $column["decimal"] + 1, "0"));
67
          $record[$column["name"]] = round(unpack("Q", $sub_data)[1] / $decimal, $column["decimal"]);
68
          break;
69
        case "I":
70
          $record[$column["name"]] = unpack("l", $sub_data)[1];
71
          break;
72
        case "@":
73
        case "T":
74
          $record[$column["name"]] = $this->getDateTime($sub_data);
75
          break;
76
        case "D":
77
          $record[$column["name"]] = empty($sub_data) ? null : $sub_data;
78
          break;
79
        case "L":
80
          $record[$column["name"]] =  ($sub_data == "?" || empty($sub_data)) ? null : (in_array(strtolower($sub_data), $this->logicals));
81
          break;
82
        case "C":
83
          $record[$column["name"]] = $this->convertChar($sub_data);
84
          break;
85
        case "M":
86
        case "P":
87
        case "G":
88
          $sub_data = (strlen($sub_data) == 4) ? unpack("L", $sub_data)[1] : (int)$sub_data;
89
          if (!$sub_data) {
90
            $record[$column["name"]] = "";
91
          } else {
92
            $record[$column["name"]] = $this->getMemo($sub_data, ($column["type"] == "M"));
93
          }
94
          break;
95
        case "0":
96
          $value = intval(unpack("C*", $sub_data)[1]);
97
          $record[$column["name"]] = $value;
98
          foreach ($this->nullFlagColumns as $index => $name) {
99
            if (($value >> $index) & 1) {
100
              $record[$name] = null;
101
            }
102
          }
103
          break;
104
      }
105
      $this->checkNullColumn($column);
106
      $pos += $column["length"];
107
    }
108
    $this->records++;
109
    return $record;
110
  }
111
112
  private function checkNullColumn($column) {
113
    if (!empty($column["has_null"])) {
114
      $this->nullFlagColumns[] = $column["name"];
115
    }
116
  }
117
118
  private function getMemo($data, $convert = true) {
119
    $memo = $this->memo->getMemo($data);
120
    return ($convert) ? $this->convertChar($memo["text"]) : $memo["text"];
121
  }
122
123
  private function convertChar($data) {
124
    return iconv($this->headers["charset_name"], $this->encode, str_replace("\r\n", "\n", $data));
125
  }
126
127
  private function getDateTime($data) {
128
    $data = trim($data);
129
    if (empty($data)) {
130
      return null;
131
    }
132
    if (strlen($data) == 14) {
133
      return $data;
134
    }
135
    $dateData = unpack("L", substr($data, 0, 4))[1];
136
    $timeData = unpack("L", substr($data, 4, 4))[1];
137
    return gmdate("YmdHis", jdtounix($dateData) + intval($timeData / 1000));
138
  }
139
}
140