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 ( 24ae15...d40ff0 )
by t
05:40 queued 03:33
created

LocalFile   F

Complexity

Total Complexity 92

Size/Duplication

Total Lines 607
Duplicated Lines 0 %

Test Coverage

Coverage 19.03%

Importance

Changes 7
Bugs 4 Features 0
Metric Value
eloc 194
c 7
b 4
f 0
dl 0
loc 607
ccs 43
cts 226
cp 0.1903
rs 2
wmc 92

43 Methods

Rating   Name   Duplication   Size   Complexity  
A __hash() 0 3 1
A attribute() 0 4 1
A __file() 0 3 1
C __construct() 0 74 15
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 7 2
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 7 2
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 chgrp() 0 10 4
A chown() 0 10 4
A downloadFile() 0 3 1
A chmod() 0 10 4
A getFilename() 0 3 1

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 18
    public function __construct($locale = 'zh_CN.UTF-8')
50
    {
51 18
        setlocale(LC_ALL, $locale);
52 18
        clearstatcache();
53
        $this->_functions['loader'] = function ($fileName) {
54 3
            $hashName = $this->__hash($fileName);
55 3
            $this->_attributes[$hashName] = I::get($this->_attributes, $hashName, [
56 3
                'isCached' => false,
57
                'isLocal' => true,
58 3
                'file' => $this->__file($fileName),
59
                // 以下属性需要重新设置
60
                'isExists' => false,
61 3
                'fileSize' => 0,
62
                'spl' => null,
63
                'splInfo' => null,
64
            ]);
65
            // 如果已经被缓存了,直接返回
66 3
            if (true === $this->_attributes[$hashName]['isCached']) {
67
                return;
68
            }
69 3
            $this->_attributes[$hashName]['isCached'] = true;
70 3
            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 = (string) I::get($url, 'path', '/');
96
                    $port = (int) 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 3
                $this->_attributes[$hashName]['isLocal'] = true;
119 3
                if ($this->_attributes[$hashName]['isExists'] = file_exists($this->_attributes[$hashName]['file'])) {
120 3
                    $this->_attributes[$hashName]['fileSize'] = filesize($this->_attributes[$hashName]['file']);
121 3
                    $this->_attributes[$hashName]['spl'] = new \SplFileObject($this->_attributes[$hashName]['file']);
122 3
                    $this->_attributes[$hashName]['splInfo'] = new \SplFileInfo($this->_attributes[$hashName]['file']);
123
                }
124
            }
125 3
        };
126 18
    }
127
128
    /**
129
     * 获取 Hash 值
130
     *
131
     * @param string $fileName
132
     *
133
     * @return string
134
     */
135 3
    private function __hash($fileName)
136
    {
137 3
        return md5($fileName);
138
    }
139
140
    /**
141
     * 返回路径别名
142
     *
143
     * @param string $file
144
     *
145
     * @return string
146
     */
147 3
    private function __file($file)
148
    {
149 3
        return (string) I::getAlias($file);
150
    }
151
152
    /**
153
     * 获取网络文件的属性
154
     *
155
     * @param string $fileName
156
     * @param string $name
157
     *
158
     * @return mixed
159
     */
160 3
    public function attribute($fileName, $name)
161
    {
162 3
        I::call($this->_functions['loader'], [$fileName]);
163 3
        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
        $spl = $this->attribute($fileName, 'spl');
176
        if ($spl instanceof \SplFileObject) {
177
            return $spl;
178
        }
179
        throw new Exception('非本地文件,不支持 spl 属性');
180
    }
181
182
    /**
183
     * 获取文件信息对象
184
     *
185
     * @param string $fileName
186
     *
187
     * @return \SplFileInfo
188
     */
189
    public function splInfo($fileName)
190
    {
191
        $splInfo = $this->attribute($fileName, 'splInfo');
192
        if ($splInfo instanceof \SplFileInfo) {
193
            return $splInfo;
194
        }
195
        throw new Exception('非本地文件,不支持 splInfo 属性');
196
    }
197
198
    /**
199
     * 遍历行的生成器
200
     *
201
     * - 自动关闭后再次调用需要重新读取文件,不建议自动关闭
202
     *
203
     * @param string $fileName
204
     * @param boolean $autoClose 是否自动关闭文件,默认 false
205
     *
206
     * @return \Generator
207
     */
208
    public function linesGenerator($fileName, $autoClose = false)
209
    {
210
        try {
211
            $spl = $this->spl($fileName);
212
            while (false === $spl->eof() && $line = $spl->fgets()) {
213
                yield $line;
214
            }
215
        } finally {
216
            true === $autoClose && $this->close($fileName);
217
        }
218
    }
219
220
    /**
221
     * 返回文本的某行
222
     *
223
     * - 每取一行,文件指针会回到初始位置,如果需要大量的行,请直接使用 linesGenerator
224
     * - 自动关闭后再次调用需要重新读取文件,不建议自动关闭
225
     *
226
     * @param string $fileName
227
     * @param integer $num 行号
228
     * @param boolean $autoClose 是否自动关闭文件,默认 false
229
     *
230
     * @return string
231
     */
232
    public function line($fileName, $num = 0, $autoClose = false)
233
    {
234
        foreach ($this->linesGenerator($fileName, $autoClose) as $k => $line) {
235
            if ($k == $num) {
236
                $this->spl($fileName)->rewind();
237
                return $line;
238
            }
239
        }
240
    }
241
242
    /**
243
     * 遍历字节的生成器
244
     *
245
     * @param string $fileName
246
     * @param boolean $autoClose 是否自动关闭文件,默认 false
247
     * @param integer $buffer 缓冲区长度,默认 1024
248
     *
249
     * @return \Generator
250
     */
251
    public function dataGenerator($fileName, $autoClose = false, $buffer = 1024)
252
    {
253
        $bufferSize = 0;
254
        try {
255
            while (!$this->spl($fileName)->eof() && $this->splInfo($fileName)->getSize() > $bufferSize) {
256
                $bufferSize += $buffer;
257
                yield $this->spl($fileName)->fread($bufferSize);
258
            }
259
        } finally {
260
            true === $autoClose && $this->close($fileName);
261
        }
262
    }
263
264
    /**
265
     * @ignore
266
     */
267
    public function getATime($fileName)
268
    {
269
        return fileatime($this->__file($fileName));
270
    }
271
272
    /**
273
     * @ignore
274
     */
275
    public function getBasename($path, $suffix = null)
276
    {
277
        return parent::getBasename($this->__file($path), $suffix);
278
    }
279
280
    /**
281
     * @ignore
282
     */
283
    public function getCTime($fileName)
284
    {
285
        return filectime($this->__file($fileName));
286
    }
287
288
    /**
289
     * @ignore
290
     */
291
    public function getExtension($fileName)
292
    {
293
        return pathinfo($this->__file($fileName), PATHINFO_EXTENSION);
294
    }
295
296
    /**
297
     * @ignore
298
     */
299
    public function getFilename($fileName)
300
    {
301
        return pathinfo($this->__file($fileName), PATHINFO_FILENAME);
302
    }
303
304
    /**
305
     * @ignore
306
     */
307
    public function getMtime($fileName)
308
    {
309
        return filemtime($this->__file($fileName));
310
    }
311
312
    /**
313
     * @ignore
314
     */
315
    public function getDirname($path)
316
    {
317
        return parent::getDirname($this->__file($path));
318
    }
319
320
    /**
321
     * @ignore
322
     */
323
    public function getPerms($path)
324
    {
325
        return fileperms($this->__file($path));
326
    }
327
328
    /**
329
     * @ignore
330
     */
331
    public function getFilesize($fileName)
332
    {
333
        return (int) $this->attribute($fileName, 'fileSize');
334
    }
335
336
    /**
337
     * @ignore
338
     */
339
    public function getType($path)
340
    {
341
        return filetype($this->__file($path));
342
    }
343
344
    /**
345
     * @ignore
346
     */
347
    public function isDir($dir)
348
    {
349
        return is_dir($this->__file($dir));
350
    }
351
352
    /**
353
     * @ignore
354
     */
355
    public function isDot($dir)
356
    {
357
        return in_array($this->getBasename($dir), ['.', '..']);
358
    }
359
360
    /**
361
     * @ignore
362
     */
363 3
    public function isFile($file)
364
    {
365 3
        return (bool) $this->attribute($file, 'isExists');
366
    }
367
368
    /**
369
     * @ignore
370
     */
371
    public function isLink($link)
372
    {
373
        return is_link($this->__file($link));
374
    }
375
376
    /**
377
     * @ignore
378
     */
379
    public function isReadable($path)
380
    {
381
        return is_readable($this->__file($path));
382
    }
383
384
    /**
385
     * @ignore
386
     */
387
    public function isWritable($path)
388
    {
389
        return is_writable($this->__file($path));
390
    }
391
392
    /**
393
     * @ignore
394
     */
395
    public function getCommandResult($command)
396
    {
397
        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...
398
    }
399
400
    /**
401
     * @ignore
402
     */
403 1
    public function getRealpath($path)
404
    {
405 1
        return realpath($this->__file($path));
406
    }
407
408
    /**
409
     * @ignore
410
     */
411
    public function getLists($dir = null, $flags = FileConstants::COMPLETE_PATH)
412
    {
413
        null === $dir && $dir = $this->getRealpath('./');
414
        $dir = $this->__file(rtrim($dir, '/') . '/');
415
        $iterator = new \RecursiveDirectoryIterator($dir, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS);
416
        if (I::hasFlag($flags, FileConstants::RECURSIVE)) {
417
            $iterator = new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::CHILD_FIRST);
418
        }
419
        $files = [];
420
        /**
421
         * @var \RecursiveDirectoryIterator $file
422
         */
423
        foreach ($iterator as $file) {
424
            if (I::hasFlag($flags, FileConstants::COMPLETE_PATH)) {
425
                $files[] = $file->getPathname();
426
            } else {
427
                $files[] = $file->getFilename();
428
            }
429
        }
430
        return $files;
431
    }
432
433
    /**
434
     * @ignore
435
     */
436 1
    public function getFileContent($file)
437
    {
438 1
        return file_get_contents($this->__file($file));
439
    }
440
441
    /**
442
     * @ignore
443
     */
444
    public function putFileContent($file, $string, $mode = 0777)
445
    {
446
        $file = $this->__file($file);
447
        $this->createDir($this->getDirname($file), $mode);
448
        $isCreated = false !== file_put_contents($file, $string);
449
        $this->chmod($file, $mode, FileConstants::RECURSIVE_DISABLED);
450
        return $isCreated;
451
    }
452
453
    /**
454
     * @ignore
455
     */
456 1
    public function deleteFile($file)
457
    {
458 1
        $file = $this->__file($file);
459 1
        if ($this->isFile($file)) {
460 1
            $this->close($file);
461 1
            return unlink($file);
462
        }
463
        return true;
464
    }
465
466
    /**
467
     * @ignore
468
     */
469
    public function uploadFile($toFile, $fromFile = null, $overwrite = true)
470
    {
471
        return false;
472
    }
473
474
    /**
475
     * @ignore
476
     */
477
    public function downloadFile($fromFile, $toFile = null, $overwrite = true)
478
    {
479
        return false;
480
    }
481
482
    /**
483
     * 客户端向服务端发起下载请求
484
     *
485
     * @param string|array $fileName 如果是数组,第一个元素是原名,第二个元素为下载名,原名需要指定路径,下载名不需要
486
     * @param callback $callback 下载完成后的回调,参数列表:文件属性数组
487
     *
488
     * @return void
489
     * @throws Exception
490
     */
491
    public function download($fileName, $callback = null)
492
    {
493
        set_time_limit(0);
494
        if (is_string($fileName)) {
495
            $fileName = [$fileName, Charset::toCn($this->getBasename($fileName))];
496
        }
497
        list($originName, $downloadName) = $fileName;
498
        $originName = $this->__file($originName);
499
        try {
500
            if ($this->isFile($originName)) {
501
                header('Content-type:application/octet-stream');
502
                header('Accept-Ranges:bytes');
503
                header('Content-Length:' . $this->getFilesize($originName));
504
                header('Content-Disposition: attachment; filename=' . $downloadName);
505
                foreach ($this->dataGenerator($originName) as $data) {
506
                    echo $data;
507
                }
508
            }
509
        } catch (Exception $e) {
510
            Header::notFound();
511
            throw $e;
512
        } finally {
513
            I::call($callback, [$this->_attributes]);
514
            // 必须要终止掉,防止发送其他数据导致错误
515
            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...
516
        }
517
    }
518
519
    /**
520
     * @ignore
521
     */
522
    public function chown($file, $user, $flags = FileConstants::RECURSIVE_DISABLED)
523
    {
524
        $file = $this->__file($file);
525
        if ($this->isDir($file) && I::hasFlag($flags, FileConstants::RECURSIVE)) {
526
            $files = $this->getLists($file, FileConstants::COMPLETE_PATH | FileConstants::RECURSIVE);
527
            foreach ($files as $subFile) {
528
                chown($subFile, $user);
529
            }
530
        }
531
        return chown($file, $user);
532
    }
533
534
    /**
535
     * @ignore
536
     */
537
    public function chgrp($file, $group, $flags = FileConstants::RECURSIVE_DISABLED)
538
    {
539
        $file = $this->__file($file);
540
        if ($this->isDir($file) && I::hasFlag($flags, FileConstants::RECURSIVE)) {
541
            $files = $this->getLists($file, FileConstants::COMPLETE_PATH | FileConstants::RECURSIVE);
542
            foreach ($files as $subFile) {
543
                chgrp($subFile, $group);
544
            }
545
        }
546
        return chgrp($file, $group);
547
    }
548
549
    /**
550
     * @ignore
551
     */
552
    public function chmod($file, $mode = 0777, $flags = FileConstants::RECURSIVE_DISABLED)
553
    {
554
        $file = $this->__file($file);
555
        if ($this->isDir($file) && I::hasFlag($flags, FileConstants::RECURSIVE)) {
556
            $files = $this->getLists($file, FileConstants::COMPLETE_PATH | FileConstants::RECURSIVE);
557
            foreach ($files as $subFile) {
558
                chmod($subFile, $mode);
559
            }
560
        }
561
        return (bool) chmod($file, $mode);
562
    }
563
564
    /**
565
     * @ignore
566
     */
567
    public function symlink($from, $to)
568
    {
569
        $from = $this->__file($from);
570
        $to = $this->__file($to);
571
        return symlink($from, $to);
572
    }
573
574
    /**
575
     * @ignore
576
     */
577 1
    public function close($fileName = null)
578
    {
579 1
        if (is_string($fileName)) {
580 1
            $fileName = [$this->__hash($fileName)];
581
        } elseif (is_array($fileName)) {
582
            foreach ($fileName as $k => $name) {
583
                $fileName[$k] = $this->__hash($name);
584
            }
585
        }
586 1
        foreach ($this->_attributes as $hashName => /** @scrutinizer ignore-unused */$attribute) {
587 1
            if (null === $fileName || is_array($fileName) && in_array($hashName, $fileName)) {
588 1
                unset($this->_attributes[$hashName]);
589
            }
590
        }
591 1
        return true;
592
    }
593
594
    /**
595
     * @ignore
596
     */
597
    protected function _copy($fromFile, $toFile)
598
    {
599
        $fromFile = $this->__file($fromFile);
600
        $toFile = $this->__file($toFile);
601
        return copy($fromFile, $toFile);
602
    }
603
604
    /**
605
     * @ignore
606
     */
607
    protected function _move($fromFile, $toFile)
608
    {
609
        $fromFile = $this->__file($fromFile);
610
        $toFile = $this->__file($toFile);
611
        return rename($fromFile, $toFile);
612
    }
613
614
    /**
615
     * @ignore
616
     */
617
    protected function _mkdir($dir, $mode = 0777)
618
    {
619
        $dir = $this->__file($dir);
620
        return mkdir($dir, $mode);
621
    }
622
623
    /**
624
     * @ignore
625
     */
626
    protected function _rmdir($dir)
627
    {
628
        $dir = $this->__file($dir);
629
        return rmdir($dir);
630
    }
631
632
}
633