Passed
Push — master ( 96b3cc...8ff2ce )
by smiley
01:25
created

ReaderAbstract   A

Complexity

Total Complexity 33

Size/Duplication

Total Lines 256
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 33
eloc 77
dl 0
loc 256
rs 9.76
c 0
b 0
f 0

11 Methods

Rating   Name   Duplication   Size   Complexity  
A saveToFile() 0 7 2
A decodeString() 0 2 1
A toCSV() 0 22 3
A __destruct() 0 4 2
A toXML() 0 25 4
A toJSON() 0 10 2
A __construct() 0 7 2
A __get() 0 2 3
A loadFile() 0 20 4
A checkData() 0 4 2
B toDB() 0 28 8
1
<?php
2
/**
3
 * Class ReaderAbstract
4
 *
5
 * @filesource   ReaderAbstract.php
6
 * @created      05.01.2019
7
 * @package      codemasher\WildstarDB
8
 * @author       smiley <[email protected]>
9
 * @copyright    2019 smiley
10
 * @license      MIT
11
 */
12
13
namespace codemasher\WildstarDB;
14
15
use chillerlan\Database\Database;
16
use Psr\Log\{LoggerAwareInterface, LoggerAwareTrait, LoggerInterface, NullLogger};
17
18
/**
19
 * @property string $file
20
 * @property array  $header
21
 * @property string $name
22
 * @property array  $cols
23
 * @property array  $data
24
 */
25
abstract class ReaderAbstract implements ReaderInterface, LoggerAwareInterface{
26
	use LoggerAwareTrait;
27
28
	/**
29
	 * @see http://php.net/manual/function.pack.php
30
	 * @var string
31
	 */
32
	protected $FORMAT_HEADER;
33
34
	/**
35
	 * @var string
36
	 */
37
	protected $file = '';
38
39
	/**
40
	 * @var string
41
	 */
42
	protected $name = '';
43
44
	/**
45
	 * @var array
46
	 */
47
	protected $header = [];
48
49
	/**
50
	 * @var array
51
	 */
52
	protected $cols = [];
53
54
	/**
55
	 * @var array
56
	 */
57
	protected $data = [];
58
59
	/**
60
	 * @var resource
61
	 */
62
	protected $fh;
63
64
	/**
65
	 * DTBLReader constructor.
66
	 *
67
	 * @param \Psr\Log\LoggerInterface|null $logger
68
	 *
69
	 * @throws \codemasher\WildstarDB\WSDBException
70
	 */
71
	public function __construct(LoggerInterface $logger = null){
72
73
		if(PHP_INT_SIZE < 8){
74
			throw new WSDBException('64-bit PHP required');
75
		}
76
77
		$this->setLogger($logger ?? new NullLogger);
78
	}
79
80
	/**
81
	 * @return void
82
	 */
83
	public function __destruct(){
84
85
		if(is_resource($this->fh)){
86
			fclose($this->fh);
87
		}
88
89
	}
90
91
	/**
92
	 * @param string $name
93
	 *
94
	 * @return mixed|null
95
	 */
96
	public function __get(string $name){
97
		return property_exists($this, $name) && $name !== 'fh' ? $this->{$name} : null;
98
	}
99
100
	/**
101
	 * @param string $filename
102
	 *
103
	 * @return void
104
	 * @throws \codemasher\WildstarDB\WSDBException
105
	 */
106
	protected function loadFile(string $filename):void{
107
108
		if(!is_file($filename) || !is_readable($filename)){
109
			throw new WSDBException('input file not readable');
110
		}
111
112
		$this->file = $filename;
113
114
		$this->logger->info('loading: '.$this->file);
115
116
		$this->fh   = fopen($this->file, 'rb');
0 ignored issues
show
Documentation Bug introduced by
It seems like fopen($this->file, 'rb') can also be of type false. However, the property $fh is declared as type resource. 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...
117
		$header     = fread($this->fh, 0x60);
0 ignored issues
show
Bug introduced by
It seems like $this->fh can also be of type false; however, parameter $handle of fread() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

117
		$header     = fread(/** @scrutinizer ignore-type */ $this->fh, 0x60);
Loading history...
118
		$this->cols = [];
119
		$this->data = [];
120
121
		if(strlen($header) !== 0x60){
122
			throw new WSDBException('cannot read header');
123
		}
124
125
		$this->header = unpack($this->FORMAT_HEADER, $header);
126
	}
127
128
	/**
129
	 * @param string $str
130
	 *
131
	 * @return string
132
	 */
133
	protected function decodeString(string $str):string{
134
		return trim(mb_convert_encoding($str, 'UTF-8', 'UTF-16LE'));
135
	}
136
137
	/**
138
	 * @return void
139
	 * @throws \codemasher\WildstarDB\WSDBException
140
	 */
141
	protected function checkData():void{
142
143
		if(empty($this->data)){
144
			throw new WSDBException('empty data, run DTBLReader::read() first');
145
		}
146
147
	}
148
149
	/**
150
	 * @param string $data
151
	 * @param string $file
152
	 *
153
	 * @return bool
154
	 * @throws \codemasher\WildstarDB\WSDBException
155
	 */
156
	protected function saveToFile(string $data, string $file):bool{
157
158
		if(!is_writable(dirname($file))){
159
			throw new WSDBException('cannot write data to file: '.$file.', target directory is not writable');
160
		}
161
162
		return (bool)file_put_contents($file, $data);
163
	}
164
165
	/**
166
	 * @param string|null $file
167
	 * @param int|null    $jsonOptions
168
	 *
169
	 * @return string
170
	 */
171
	public function toJSON(string $file = null, int $jsonOptions = 0):string{
172
		$this->checkData();
173
174
		$json = json_encode($this->data, $jsonOptions);
175
176
		if($file !== null){
177
			$this->saveToFile($json, $file);
178
		}
179
180
		return $json;
181
	}
182
183
	/**
184
	 * @param string|null $file
185
	 * @param string      $delimiter
186
	 * @param string      $enclosure
187
	 * @param string      $escapeChar
188
	 *
189
	 * @return string
190
	 */
191
	public function toCSV(string $file = null, string $delimiter = ',', string $enclosure = '"', string $escapeChar = '\\'):string{
192
		$this->checkData();
193
194
		$mh = fopen('php://memory', 'r+');
195
196
		fputcsv($mh, array_column($this->cols, 'name'), $delimiter, $enclosure, $escapeChar);
0 ignored issues
show
Bug introduced by
It seems like $mh can also be of type false; however, parameter $handle of fputcsv() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

196
		fputcsv(/** @scrutinizer ignore-type */ $mh, array_column($this->cols, 'name'), $delimiter, $enclosure, $escapeChar);
Loading history...
197
198
		foreach($this->data as $row){
199
			fputcsv($mh, array_values($row), $delimiter, $enclosure, $escapeChar);
200
		}
201
202
		rewind($mh);
0 ignored issues
show
Bug introduced by
It seems like $mh can also be of type false; however, parameter $handle of rewind() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

202
		rewind(/** @scrutinizer ignore-type */ $mh);
Loading history...
203
204
		$csv = stream_get_contents($mh);
0 ignored issues
show
Bug introduced by
It seems like $mh can also be of type false; however, parameter $handle of stream_get_contents() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

204
		$csv = stream_get_contents(/** @scrutinizer ignore-type */ $mh);
Loading history...
205
206
		fclose($mh);
0 ignored issues
show
Bug introduced by
It seems like $mh can also be of type false; however, parameter $handle of fclose() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

206
		fclose(/** @scrutinizer ignore-type */ $mh);
Loading history...
207
208
		if($file !== null){
209
			$this->saveToFile($csv, $file);
210
		}
211
212
		return $csv;
213
	}
214
215
	/**
216
	 * ugh!
217
	 *
218
	 * @param string|null $file
219
	 *
220
	 * @return string
221
	 */
222
	public function toXML(string $file = null):string{
223
		$this->checkData();
224
225
		$sxe = new \SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?><root></root>', LIBXML_BIGLINES);
226
227
		$types = [3 => 'uint32', 4 => 'float', 11 => 'bool', 20 => 'uint64', 130 => 'string'];
228
229
		foreach($this->data as $row){
230
			$item = $sxe->addChild('item');
231
232
			foreach(array_values($row) as $i => $value){
233
				$item
234
					->addChild($this->cols[$i]['name'], $value)
235
					->addAttribute('dataType', $types[$this->cols[$i]['header']['DataType']]);
236
				;
237
			}
238
		}
239
240
		$xml = $sxe->asXML();
241
242
		if($file !== null){
243
			$this->saveToFile($xml, $file);
244
		}
245
246
		return $xml;
247
	}
248
249
	/**
250
	 * @return void
251
	 * @param \chillerlan\Database\Database $db
252
	 */
253
	public function toDB(Database $db):void{
254
		// Windows: https://dev.mysql.com/doc/refman/8.0/en/server-system-variables.html#sysvar_lower_case_table_names
255
		$createTable = $db->create
256
			->table($this->name)
257
			->primaryKey($this->cols[0]['name'])
258
			->ifNotExists()
259
		;
260
261
		foreach($this->cols as $i => $col){
262
263
			switch($col['header']['DataType']){
264
				case 3:   $createTable->int($col['name'], 10, null, null, 'UNSIGNED'); break;
265
				case 4:   $createTable->decimal($col['name'], '7,3', 0); break;
266
				case 11:  $createTable->field($col['name'], 'BOOLEAN'); break;
267
				case 20:  $createTable->field($col['name'], 'BIGINT', null, 'UNSIGNED'); break;
268
				case 130: $createTable->text($col['name']); break;
269
			}
270
271
		}
272
273
		$createTable->query();
274
275
		if(count($this->data) < 1){
276
			$this->logger->notice('no records available for table '.$this->name);
277
			return;
278
		}
279
280
		$db->insert->into($this->name)->values($this->data)->multi();
281
	}
282
283
}
284