1
|
|
|
<?php |
2
|
|
|
namespace WebStream\IO; |
3
|
|
|
|
4
|
|
|
use WebStream\Exception\Extend\InvalidArgumentException; |
5
|
|
|
use WebStream\Exception\Extend\IOException; |
6
|
|
|
|
7
|
|
|
/** |
8
|
|
|
* FileInputStream |
9
|
|
|
* @author Ryuichi TANAKA. |
10
|
|
|
* @since 2016/02/05 |
11
|
|
|
* @version 0.7 |
12
|
|
|
*/ |
13
|
|
|
class FileInputStream extends InputStream |
14
|
|
|
{ |
15
|
|
|
/** |
16
|
|
|
* @var File ファイルオブジェクト |
17
|
|
|
*/ |
18
|
|
|
protected $file; |
19
|
|
|
|
20
|
|
|
/** |
21
|
|
|
* constructor |
22
|
|
|
* @param mixed $file ファイルオブジェクトまたはファイルパス |
23
|
|
|
* @throws InvalidArgumentException |
24
|
|
|
* @throws IOException |
25
|
|
|
*/ |
26
|
|
|
public function __construct($file) |
27
|
|
|
{ |
28
|
|
|
if ($file instanceof File) { |
29
|
|
|
$this->file = $file; |
30
|
|
|
} elseif (is_string($file)) { |
31
|
|
|
$this->file = new File($file); |
32
|
|
|
} else { |
33
|
|
|
throw new InvalidArgumentException("Unable to open file: " . $file); |
34
|
|
|
} |
35
|
|
|
|
36
|
|
|
// 読み込みはロックを掛けずダーティーリード |
37
|
|
|
$stream = fopen($this->file->getAbsoluteFilePath(), 'r'); |
38
|
|
|
if (!is_resource($stream) || $stream === false) { |
39
|
|
|
throw new IOException("Unable open " . $this->file->getAbsoluteFilePath()); |
40
|
|
|
} |
41
|
|
|
|
42
|
|
|
parent::__construct($stream); |
43
|
|
|
} |
44
|
|
|
|
45
|
|
|
/** |
46
|
|
|
* 入力ストリームを閉じる |
47
|
|
|
*/ |
48
|
|
|
public function close() |
49
|
|
|
{ |
50
|
|
|
if ($this->stream === null) { |
51
|
|
|
return; |
52
|
|
|
} |
53
|
|
|
|
54
|
|
View Code Duplication |
if (get_resource_type($this->stream) !== 'Unknown' && fclose($this->stream) === false) { |
|
|
|
|
55
|
|
|
throw new IOException("Cannot close input stream."); |
56
|
|
|
} |
57
|
|
|
|
58
|
|
|
$this->stream = null; |
59
|
|
|
} |
60
|
|
|
|
61
|
|
|
/** |
62
|
|
|
* {@inheritdoc} |
63
|
|
|
*/ |
64
|
|
|
public function read($length = null) |
65
|
|
|
{ |
66
|
|
|
if ($this->stream === null) { |
67
|
|
|
return null; |
68
|
|
|
} |
69
|
|
|
|
70
|
|
|
if ($this->eof()) { |
71
|
|
|
return null; |
72
|
|
|
} |
73
|
|
|
|
74
|
|
|
$out = null; |
|
|
|
|
75
|
|
|
if ($length === null) { |
76
|
|
View Code Duplication |
if (($out = @fread($this->stream, 1)) === false) { |
|
|
|
|
77
|
|
|
throw new IOException("Failed to read stream."); |
78
|
|
|
} |
79
|
|
|
} else { |
80
|
|
|
if (!is_int($length)) { |
81
|
|
|
throw new InvalidArgumentException("Stream read must be a numeric value."); |
82
|
|
|
} |
83
|
|
|
// ポインタ位置が負になった場合、警告が出てfalseを返す |
84
|
|
|
// ポインタの終端を越えた場合、読み込みを終了する |
85
|
|
|
// すでに終端位置の場合、空文字を返す |
86
|
|
View Code Duplication |
if (($out = @fread($this->stream, $length)) === false) { |
|
|
|
|
87
|
|
|
throw new IOException("Failed to read stream."); |
88
|
|
|
} |
89
|
|
|
} |
90
|
|
|
|
91
|
|
|
return $out; |
92
|
|
|
} |
93
|
|
|
|
94
|
|
|
/** |
95
|
|
|
* 入力ストリームから行単位でデータを読み込む |
96
|
|
|
* 末尾に改行コードは含まない |
97
|
|
|
* @return string 読み込みデータ |
98
|
|
|
*/ |
99
|
|
|
public function readLine() |
100
|
|
|
{ |
101
|
|
|
if ($this->stream === null) { |
102
|
|
|
return null; |
103
|
|
|
} |
104
|
|
|
|
105
|
|
|
if ($this->eof()) { |
106
|
|
|
return null; |
107
|
|
|
} |
108
|
|
|
|
109
|
|
|
$out = fgets($this->stream); |
110
|
|
|
if ($out === false) { |
111
|
|
|
return null; |
112
|
|
|
} |
113
|
|
|
|
114
|
|
|
$this->cursorPosition = ftell($this->stream); |
115
|
|
|
|
116
|
|
|
return trim($out); |
117
|
|
|
} |
118
|
|
|
|
119
|
|
|
/** |
120
|
|
|
* {@inheritdoc} |
121
|
|
|
*/ |
122
|
|
|
public function skip(int $pos) |
123
|
|
|
{ |
124
|
|
|
if ($this->stream === null) { |
125
|
|
|
return -1; |
126
|
|
|
} |
127
|
|
|
|
128
|
|
|
// 現在のポインタ位置から$posだけ後方へ移動 |
129
|
|
|
// シークに対応していないファイルシステムの場合、-1を返す |
130
|
|
|
if (fseek($this->stream, $pos, SEEK_CUR) === -1) { |
131
|
|
|
return -1; |
132
|
|
|
} |
133
|
|
|
|
134
|
|
|
$start = $this->cursorPosition; |
135
|
|
|
$this->cursorPosition = ftell($this->stream); |
136
|
|
|
|
137
|
|
|
$skipNum = 0; |
|
|
|
|
138
|
|
View Code Duplication |
if ($start > $this->cursorPosition) { |
|
|
|
|
139
|
|
|
// 後方へ移動 |
140
|
|
|
$skipNum = $start - $this->cursorPosition; |
141
|
|
|
} else { |
142
|
|
|
// 前方へ移動 |
143
|
|
|
$skipNum = $this->cursorPosition - $start; |
144
|
|
|
} |
145
|
|
|
|
146
|
|
|
return $skipNum; |
147
|
|
|
} |
148
|
|
|
|
149
|
|
|
/** |
150
|
|
|
* {@inheritdoc} |
151
|
|
|
*/ |
152
|
|
|
public function reset() |
153
|
|
|
{ |
154
|
|
|
if (!$this->isMarkSupported()) { |
155
|
|
|
throw new IOException(get_class($this) . " does not support mark and reset."); |
156
|
|
|
} |
157
|
|
|
|
158
|
|
|
if ($this->stream === null) { |
159
|
|
|
return; |
160
|
|
|
} |
161
|
|
|
|
162
|
|
|
// ポインタ位置をmark位置に移動 |
163
|
|
|
fseek($this->stream, $this->markedPosition, SEEK_SET); |
164
|
|
|
// mark位置を初期値に戻す |
165
|
|
|
$this->cursorPosition = $this->markedPosition; |
166
|
|
|
$this->markedPosition = 0; |
167
|
|
|
} |
168
|
|
|
|
169
|
|
|
/** |
170
|
|
|
* {@inheritdoc} |
171
|
|
|
*/ |
172
|
|
|
public function eof() |
173
|
|
|
{ |
174
|
|
|
return feof($this->stream); |
175
|
|
|
} |
176
|
|
|
|
177
|
|
|
/** |
178
|
|
|
* {@inheritdoc} |
179
|
|
|
*/ |
180
|
|
|
public function isMarkSupported() |
181
|
|
|
{ |
182
|
|
|
return true; |
183
|
|
|
} |
184
|
|
|
} |
185
|
|
|
|
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.