GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — master ( 966fe3...40d637 )
by t
04:58 queued 02:23
created

LocalFile   F

Complexity

Total Complexity 90

Size/Duplication

Total Lines 599
Duplicated Lines 0 %

Test Coverage

Coverage 19.55%

Importance

Changes 6
Bugs 4 Features 0
Metric Value
eloc 188
c 6
b 4
f 0
dl 0
loc 599
ccs 43
cts 220
cp 0.1955
rs 2
wmc 90

43 Methods

Rating   Name   Duplication   Size   Complexity  
A __hash() 0 3 1
A _copy() 0 5 1
A getCommandResult() 0 3 1
A isDir() 0 3 1
A getMtime() 0 3 1
A dataGenerator() 0 10 4
A linesGenerator() 0 9 4
A putFileContent() 0 7 1
A getFileContent() 0 3 1
A isReadable() 0 3 1
A download() 0 25 5
A _rmdir() 0 4 1
A getLists() 0 20 5
A _move() 0 5 1
A getFilesize() 0 3 1
A splInfo() 0 3 1
A symlink() 0 5 1
A getPerms() 0 3 1
A getBasename() 0 3 1
A _mkdir() 0 4 1
A getExtension() 0 3 1
A getATime() 0 3 1
A line() 0 6 3
A spl() 0 3 1
A getRealpath() 0 3 1
A getCTime() 0 3 1
B close() 0 15 8
A isDot() 0 3 1
A getDirname() 0 3 1
A uploadFile() 0 3 1
A isLink() 0 3 1
A deleteFile() 0 8 2
A isWritable() 0 3 1
A isFile() 0 3 1
A getType() 0 3 1
A attribute() 0 4 1
A chgrp() 0 10 4
A chown() 0 10 4
A downloadFile() 0 3 1
A __file() 0 3 1
A chmod() 0 10 4
A getFilename() 0 3 1
C __construct() 0 74 15

How to fix   Complexity   

Complex Class

Complex classes like LocalFile 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 LocalFile, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Class LocalFile
4
 *
5
 * @link https://www.icy2003.com/
6
 * @author icy2003 <[email protected]>
7
 * @copyright Copyright (c) 2019, icy2003
8
 */
9
namespace icy2003\php\icomponents\file;
10
11
use Exception;
12
use icy2003\php\I;
13
use icy2003\php\icomponents\file\FileInterface;
14
use icy2003\php\ihelpers\Charset;
15
use icy2003\php\ihelpers\Header;
16
17
/**
18
 * 本地文件
19
 *
20
 * - 支持本地文件操作
21
 * - 支持网络文件:文件是否存在、文件大小
22
 */
23
class LocalFile extends Base implements FileInterface
24
{
25
26
    /**
27
     * 加载文件用的函数
28
     *
29
     * @var callback
30
     */
31
    protected $_functions = [
32
        'loader' => null,
33
    ];
34
35
    /**
36
     * 文件属性
37
     *
38
     * - 文件名为键,属性为值
39
     *
40
     * @var array
41
     */
42
    protected $_attributes = [];
43
44
    /**
45
     * 初始化
46
     *
47
     * @param mixed $locale 地区信息
48
     */
49 2
    public function __construct($locale = 'zh_CN.UTF-8')
50
    {
51 2
        setlocale(LC_ALL, $locale);
52 2
        clearstatcache();
53
        $this->_functions['loader'] = function ($fileName) {
54 2
            $hashName = $this->__hash($fileName);
55 2
            $this->_attributes[$hashName] = I::get($this->_attributes, $hashName, [
56 2
                'isCached' => false,
57
                'isLocal' => true,
58 2
                'file' => $this->__file($fileName),
59
                // 以下属性需要重新设置
60
                'isExists' => false,
61 2
                'fileSize' => 0,
62
                'spl' => null,
63
                'splInfo' => null,
64
            ]);
65
            // 如果已经被缓存了,直接返回
66 2
            if (true === $this->_attributes[$hashName]['isCached']) {
67
                return;
68
            }
69 2
            $this->_attributes[$hashName]['isCached'] = true;
70 2
            if (preg_match('/^https?:\/\//', $fileName)) {
71
                $this->_attributes[$hashName]['isLocal'] = false;
72
                // 加载网络文件
73
                if (extension_loaded('curl')) {
74
                    $curl = curl_init($fileName);
75
                    curl_setopt($curl, CURLOPT_NOBODY, true);
0 ignored issues
show
Bug introduced by
It seems like $curl can also be of type false; however, parameter $ch of curl_setopt() 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

75
                    curl_setopt(/** @scrutinizer ignore-type */ $curl, CURLOPT_NOBODY, true);
Loading history...
76
                    curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
77
                    curl_setopt($curl, CURLOPT_HEADER, true);
78
                    $result = curl_exec($curl);
0 ignored issues
show
Bug introduced by
It seems like $curl can also be of type false; however, parameter $ch of curl_exec() 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

78
                    $result = curl_exec(/** @scrutinizer ignore-type */ $curl);
Loading history...
79
                    if ($result && $info = curl_getinfo($curl)) {
0 ignored issues
show
Bug introduced by
It seems like $curl can also be of type false; however, parameter $ch of curl_getinfo() 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

79
                    if ($result && $info = curl_getinfo(/** @scrutinizer ignore-type */ $curl)) {
Loading history...
80
                        if (200 == $info['http_code']) {
81
                            $this->_attributes[$hashName]['isExists'] = true;
82
                            $this->_attributes[$hashName]['fileSize'] = $info['download_content_length'];
83
                        }
84
                    }
85
                    curl_close($curl);
0 ignored issues
show
Bug introduced by
It seems like $curl can also be of type false; however, parameter $ch of curl_close() 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

85
                    curl_close(/** @scrutinizer ignore-type */ $curl);
Loading history...
86
                } elseif ((bool) ini_get('allow_url_fopen')) {
87
                    $headArray = (array) get_headers($fileName, 1);
88
                    if (preg_match('/200/', $headArray[0])) {
89
                        $this->_attributes[$hashName]['isExists'] = true;
90
                        $this->_attributes[$hashName]['fileSize'] = $headArray['Content-Length'];
91
                    }
92
                } else {
93
                    $url = parse_url($fileName);
94
                    $host = $url['host'];
95
                    $path = I::get($url, 'path', '/');
96
                    $port = I::get($url, 'port', 80);
97
                    $fp = fsockopen($host, $port);
98
                    if (is_resource($fp)) {
99
                        $header = [
100
                            'GET ' . $path . ' HTTP/1.0',
101
                            'HOST: ' . $host . ':' . $port,
102
                            'Connection: Close',
103
                        ];
104
                        fwrite($fp, implode('\r\n', $header) . '\r\n\r\n');
105
                        while (!feof($fp)) {
106
                            $line = fgets($fp);
107
                            if ('' == trim($line)) {
108
                                break;
109
                            } else {
110
                                preg_match('/HTTP.*(\s\d{3}\s)/', $line, $arr) && $this->_attributes[$hashName]['isExists'] = true;
111
                                preg_match('/Content-Length:(.*)/si', $line, $arr) && $this->_attributes[$hashName]['fileSize'] = trim($arr[1]);
112
                            }
113
                        }
114
                    }
115
                    fclose($fp);
0 ignored issues
show
Bug introduced by
It seems like $fp 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

115
                    fclose(/** @scrutinizer ignore-type */ $fp);
Loading history...
116
                }
117
            } else {
118 2
                $this->_attributes[$hashName]['isLocal'] = true;
119 2
                if ($this->_attributes[$hashName]['isExists'] = file_exists($this->_attributes[$hashName]['file'])) {
120 2
                    $this->_attributes[$hashName]['fileSize'] = filesize($this->_attributes[$hashName]['file']);
121 2
                    $this->_attributes[$hashName]['spl'] = new \SplFileObject($this->_attributes[$hashName]['file']);
122 2
                    $this->_attributes[$hashName]['splInfo'] = new \SplFileInfo($this->_attributes[$hashName]['file']);
123
                }
124
            }
125 2
        };
126 2
    }
127
128
    /**
129
     * 获取 Hash 值
130
     *
131
     * @param string $fileName
132
     *
133
     * @return string
134
     */
135 2
    private function __hash($fileName)
136
    {
137 2
        return md5($fileName);
138
    }
139
140
    /**
141
     * 返回路径别名
142
     *
143
     * @param string $file
144
     *
145
     * @return string
146
     */
147 2
    private function __file($file)
148
    {
149 2
        return (string) I::getAlias($file);
150
    }
151
152
    /**
153
     * 获取网络文件的属性
154
     *
155
     * @param string $fileName
156
     * @param string $name
157
     *
158
     * @return mixed
159
     */
160 2
    public function attribute($fileName, $name)
161
    {
162 2
        I::trigger($this->_functions['loader'], [$fileName]);
163 2
        return I::get($this->_attributes, $this->__hash($fileName) . '.' . $name);
164
    }
165
166
    /**
167
     * 获取文件对象
168
     *
169
     * @param string $fileName
170
     *
171
     * @return \SplFileObject
172
     */
173
    public function spl($fileName)
174
    {
175
        return $this->attribute($fileName, 'spl');
176
    }
177
178
    /**
179
     * 获取文件信息对象
180
     *
181
     * @param string $fileName
182
     *
183
     * @return \SplFileInfo
184
     */
185
    public function splInfo($fileName)
186
    {
187
        return $this->attribute($fileName, 'splInfo');
188
    }
189
190
    /**
191
     * 遍历行的生成器
192
     *
193
     * - 自动关闭后再次调用需要重新读取文件,不建议自动关闭
194
     *
195
     * @param string $fileName
196
     * @param boolean $autoClose 是否自动关闭文件,默认 false
197
     *
198
     * @return \Generator
199
     */
200
    public function linesGenerator($fileName, $autoClose = false)
201
    {
202
        try {
203
            $spl = $this->spl($fileName);
204
            while (false === $spl->eof() && $line = $spl->fgets()) {
205
                yield $line;
206
            }
207
        } finally {
208
            true === $autoClose && $this->close($fileName);
209
        }
210
    }
211
212
    /**
213
     * 返回文本的某行
214
     *
215
     * - 每取一行,文件指针会回到初始位置,如果需要大量的行,请直接使用 linesGenerator
216
     * - 自动关闭后再次调用需要重新读取文件,不建议自动关闭
217
     *
218
     * @param string $fileName
219
     * @param integer $num 行号
220
     * @param boolean $autoClose 是否自动关闭文件,默认 false
221
     *
222
     * @return string
223
     */
224
    public function line($fileName, $num = 0, $autoClose = false)
225
    {
226
        foreach ($this->linesGenerator($fileName, $autoClose) as $k => $line) {
227
            if ($k == $num) {
228
                $this->spl($fileName)->rewind();
229
                return $line;
230
            }
231
        }
232
    }
233
234
    /**
235
     * 遍历字节的生成器
236
     *
237
     * @param string $fileName
238
     * @param boolean $autoClose 是否自动关闭文件,默认 false
239
     * @param integer $buffer 缓冲区长度,默认 1024
240
     *
241
     * @return \Generator
242
     */
243
    public function dataGenerator($fileName, $autoClose = false, $buffer = 1024)
244
    {
245
        $bufferSize = 0;
246
        try {
247
            while (!$this->spl($fileName)->eof() && $this->splInfo($fileName)->getSize() > $bufferSize) {
248
                $bufferSize += $buffer;
249
                yield $this->spl($fileName)->fread($bufferSize);
250
            }
251
        } finally {
252
            true === $autoClose && $this->close($fileName);
253
        }
254
    }
255
256
    /**
257
     * @ignore
258
     */
259
    public function getATime($fileName)
260
    {
261
        return fileatime($this->__file($fileName));
262
    }
263
264
    /**
265
     * @ignore
266
     */
267
    public function getBasename($path, $suffix = null)
268
    {
269
        return parent::getBasename($this->__file($path), $suffix);
270
    }
271
272
    /**
273
     * @ignore
274
     */
275
    public function getCTime($fileName)
276
    {
277
        return filectime($this->__file($fileName));
278
    }
279
280
    /**
281
     * @ignore
282
     */
283
    public function getExtension($fileName)
284
    {
285
        return pathinfo($this->__file($fileName), PATHINFO_EXTENSION);
286
    }
287
288
    /**
289
     * @ignore
290
     */
291
    public function getFilename($fileName)
292
    {
293
        return pathinfo($this->__file($fileName), PATHINFO_FILENAME);
294
    }
295
296
    /**
297
     * @ignore
298
     */
299
    public function getMtime($fileName)
300
    {
301
        return filemtime($this->__file($fileName));
302
    }
303
304
    /**
305
     * @ignore
306
     */
307
    public function getDirname($path)
308
    {
309
        return parent::getDirname($this->__file($path));
310
    }
311
312
    /**
313
     * @ignore
314
     */
315
    public function getPerms($path)
316
    {
317
        return fileperms($this->__file($path));
318
    }
319
320
    /**
321
     * @ignore
322
     */
323
    public function getFilesize($fileName)
324
    {
325
        return $this->attribute($fileName, 'fileSize');
326
    }
327
328
    /**
329
     * @ignore
330
     */
331
    public function getType($path)
332
    {
333
        return filetype($this->__file($path));
334
    }
335
336
    /**
337
     * @ignore
338
     */
339
    public function isDir($dir)
340
    {
341
        return is_dir($this->__file($dir));
342
    }
343
344
    /**
345
     * @ignore
346
     */
347
    public function isDot($dir)
348
    {
349
        return in_array($this->getBasename($dir), ['.', '..']);
350
    }
351
352
    /**
353
     * @ignore
354
     */
355 2
    public function isFile($file)
356
    {
357 2
        return $this->attribute($file, 'isExists');
358
    }
359
360
    /**
361
     * @ignore
362
     */
363
    public function isLink($link)
364
    {
365
        return is_link($this->__file($link));
366
    }
367
368
    /**
369
     * @ignore
370
     */
371
    public function isReadable($path)
372
    {
373
        return is_readable($this->__file($path));
374
    }
375
376
    /**
377
     * @ignore
378
     */
379
    public function isWritable($path)
380
    {
381
        return is_writable($this->__file($path));
382
    }
383
384
    /**
385
     * @ignore
386
     */
387
    public function getCommandResult($command)
388
    {
389
        return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the return type mandated by icy2003\php\icomponents\...ase::getCommandResult() of string.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
390
    }
391
392
    /**
393
     * @ignore
394
     */
395 1
    public function getRealpath($path)
396
    {
397 1
        return realpath($this->__file($path));
398
    }
399
400
    /**
401
     * @ignore
402
     */
403
    public function getLists($dir = null, $flags = FileConstants::COMPLETE_PATH)
404
    {
405
        null === $dir && $dir = $this->getRealpath('./');
406
        $dir = $this->__file(rtrim($dir, '/') . '/');
407
        $iterator = new \RecursiveDirectoryIterator($dir, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS);
408
        if (I::hasFlag($flags, FileConstants::RECURSIVE)) {
409
            $iterator = new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::CHILD_FIRST);
410
        }
411
        $files = [];
412
        /**
413
         * @var \RecursiveDirectoryIterator $file
414
         */
415
        foreach ($iterator as $file) {
416
            if (I::hasFlag($flags, FileConstants::COMPLETE_PATH)) {
417
                $files[] = $file->getPathname();
418
            } else {
419
                $files[] = $file->getFilename();
420
            }
421
        }
422
        return $files;
423
    }
424
425
    /**
426
     * @ignore
427
     */
428 1
    public function getFileContent($file)
429
    {
430 1
        return file_get_contents($this->__file($file));
431
    }
432
433
    /**
434
     * @ignore
435
     */
436
    public function putFileContent($file, $string, $mode = 0777)
437
    {
438
        $file = $this->__file($file);
439
        $this->createDir($this->getDirname($file), $mode);
440
        $isCreated = false !== file_put_contents($file, $string);
441
        $this->chmod($file, $mode, FileConstants::RECURSIVE_DISABLED);
442
        return $isCreated;
443
    }
444
445
    /**
446
     * @ignore
447
     */
448 1
    public function deleteFile($file)
449
    {
450 1
        $file = $this->__file($file);
451 1
        if ($this->isFile($file)) {
452 1
            $this->close($file);
453 1
            return unlink($file);
454
        }
455
        return true;
456
    }
457
458
    /**
459
     * @ignore
460
     */
461
    public function uploadFile($toFile, $fromFile = null, $overwrite = true)
462
    {
463
        return false;
464
    }
465
466
    /**
467
     * @ignore
468
     */
469
    public function downloadFile($fromFile, $toFile = null, $overwrite = true)
470
    {
471
        return false;
472
    }
473
474
    /**
475
     * 客户端向服务端发起下载请求
476
     *
477
     * @param string|array $fileName 如果是数组,第一个元素是原名,第二个元素为下载名,原名需要指定路径,下载名不需要
478
     * @param callback $callback 下载完成后的回调,参数列表:文件属性数组
479
     *
480
     * @return void
481
     * @throws Exception
482
     */
483
    public function download($fileName, $callback = null)
484
    {
485
        set_time_limit(0);
486
        if (is_string($fileName)) {
487
            $fileName = [$fileName, Charset::toCn($this->getBasename($fileName))];
488
        }
489
        list($originName, $downloadName) = $fileName;
490
        $originName = $this->__file($originName);
491
        try {
492
            if ($this->isFile($originName)) {
493
                header('Content-type:application/octet-stream');
494
                header('Accept-Ranges:bytes');
495
                header('Content-Length:' . $this->getFilesize($originName));
496
                header('Content-Disposition: attachment; filename=' . $downloadName);
497
                foreach ($this->dataGenerator($originName) as $data) {
498
                    echo $data;
499
                }
500
            }
501
        } catch (Exception $e) {
502
            Header::notFound();
503
            throw $e;
504
        } finally {
505
            I::trigger($callback, [$this->_attributes]);
506
            // 必须要终止掉,防止发送其他数据导致错误
507
            die;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
508
        }
509
    }
510
511
    /**
512
     * @ignore
513
     */
514
    public function chown($file, $user, $flags = FileConstants::RECURSIVE_DISABLED)
515
    {
516
        $file = $this->__file($file);
517
        if ($this->isDir($file) && I::hasFlag($flags, FileConstants::RECURSIVE)) {
518
            $files = $this->getLists($file, FileConstants::COMPLETE_PATH | FileConstants::RECURSIVE);
519
            foreach ($files as $subFile) {
520
                chown($subFile, $user);
521
            }
522
        }
523
        return chown($file, $user);
524
    }
525
526
    /**
527
     * @ignore
528
     */
529
    public function chgrp($file, $group, $flags = FileConstants::RECURSIVE_DISABLED)
530
    {
531
        $file = $this->__file($file);
532
        if ($this->isDir($file) && I::hasFlag($flags, FileConstants::RECURSIVE)) {
533
            $files = $this->getLists($file, FileConstants::COMPLETE_PATH | FileConstants::RECURSIVE);
534
            foreach ($files as $subFile) {
535
                chgrp($subFile, $group);
536
            }
537
        }
538
        return chgrp($file, $group);
539
    }
540
541
    /**
542
     * @ignore
543
     */
544
    public function chmod($file, $mode = 0777, $flags = FileConstants::RECURSIVE_DISABLED)
545
    {
546
        $file = $this->__file($file);
547
        if ($this->isDir($file) && I::hasFlag($flags, FileConstants::RECURSIVE)) {
548
            $files = $this->getLists($file, FileConstants::COMPLETE_PATH | FileConstants::RECURSIVE);
549
            foreach ($files as $subFile) {
550
                chmod($subFile, $mode);
551
            }
552
        }
553
        return (bool) chmod($file, $mode);
554
    }
555
556
    /**
557
     * @ignore
558
     */
559
    public function symlink($from, $to)
560
    {
561
        $from = $this->__file($from);
562
        $to = $this->__file($to);
563
        return symlink($from, $to);
564
    }
565
566
    /**
567
     * @ignore
568
     */
569 1
    public function close($fileName = null)
570
    {
571 1
        if (is_string($fileName)) {
572 1
            $fileName = [$this->__hash($fileName)];
573
        } elseif (is_array($fileName)) {
574
            foreach ($fileName as $k => $name) {
575
                $fileName[$k] = $this->__hash($name);
576
            }
577
        }
578 1
        foreach ($this->_attributes as $hashName => /** @scrutinizer ignore-unused */$attribute) {
579 1
            if (null === $fileName || is_array($fileName) && in_array($hashName, $fileName)) {
580 1
                unset($this->_attributes[$hashName]);
581
            }
582
        }
583 1
        return true;
584
    }
585
586
    /**
587
     * @ignore
588
     */
589
    protected function _copy($fromFile, $toFile)
590
    {
591
        $fromFile = $this->__file($fromFile);
592
        $toFile = $this->__file($toFile);
593
        return copy($fromFile, $toFile);
594
    }
595
596
    /**
597
     * @ignore
598
     */
599
    protected function _move($fromFile, $toFile)
600
    {
601
        $fromFile = $this->__file($fromFile);
602
        $toFile = $this->__file($toFile);
603
        return rename($fromFile, $toFile);
604
    }
605
606
    /**
607
     * @ignore
608
     */
609
    protected function _mkdir($dir, $mode = 0777)
610
    {
611
        $dir = $this->__file($dir);
612
        return mkdir($dir, $mode);
613
    }
614
615
    /**
616
     * @ignore
617
     */
618
    protected function _rmdir($dir)
619
    {
620
        $dir = $this->__file($dir);
621
        return rmdir($dir);
622
    }
623
624
}
625