1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* This file is part of the NeedleProject\FileIo package. |
4
|
|
|
* |
5
|
|
|
* For the full copyright and license information, please view the LICENSE |
6
|
|
|
* file that was distributed with this source code. |
7
|
|
|
*/ |
8
|
|
|
namespace NeedleProject\FileIo; |
9
|
|
|
|
10
|
|
|
use NeedleProject\FileIo\Content\ContentInterface; |
11
|
|
|
use NeedleProject\FileIo\Exception\FileNotFoundException; |
12
|
|
|
use NeedleProject\FileIo\Exception\IOException; |
13
|
|
|
use NeedleProject\FileIo\Exception\PermissionDeniedException; |
14
|
|
|
use NeedleProject\FileIo\Factory\ContentFactory; |
15
|
|
|
use NeedleProject\FileIo\Helper\PathHelper; |
16
|
|
|
use NeedleProject\Common\Util\ErrorToExceptionConverter; |
17
|
|
|
|
18
|
|
|
/** |
19
|
|
|
* Class File |
20
|
|
|
* |
21
|
|
|
* @package NeedleProject\FileIo |
22
|
|
|
* @author Adrian Tilita <[email protected]> |
23
|
|
|
* @copyright 2017 Adrian Tilita |
24
|
|
|
* @license https://opensource.org/licenses/MIT MIT Licence |
25
|
|
|
*/ |
26
|
|
|
class File |
27
|
|
|
{ |
28
|
|
|
/** |
29
|
|
|
* File extension separator |
30
|
|
|
* @const string |
31
|
|
|
*/ |
32
|
|
|
const EXTENSION_SEPARATOR = '.'; |
33
|
|
|
|
34
|
|
|
/** |
35
|
|
|
* File's name including the path |
36
|
|
|
* @var null|string |
37
|
|
|
*/ |
38
|
|
|
private $filenameWithPath = null; |
39
|
|
|
|
40
|
|
|
/** |
41
|
|
|
* File's extension - For no extension a blank string will be used |
42
|
|
|
* @var null|string |
43
|
|
|
*/ |
44
|
|
|
private $extension = null; |
45
|
|
|
|
46
|
|
|
/** |
47
|
|
|
* File's name without extension |
48
|
|
|
* @var null|string |
49
|
|
|
*/ |
50
|
|
|
private $name = null; |
51
|
|
|
|
52
|
|
|
/** |
53
|
|
|
* Whether the file has an extension or if it is set by us |
54
|
|
|
* @var bool |
55
|
|
|
*/ |
56
|
|
|
private $hasExtension = false; |
57
|
|
|
|
58
|
|
|
/** |
59
|
|
|
* @var null|ContentFactory |
60
|
|
|
*/ |
61
|
|
|
private $contentFactory = null; |
62
|
|
|
|
63
|
|
|
/** |
64
|
|
|
* @var null|ErrorToExceptionConverter |
65
|
|
|
*/ |
66
|
|
|
private $errorHandler = null; |
67
|
|
|
|
68
|
|
|
/** |
69
|
|
|
* File constructor. |
70
|
|
|
* |
71
|
|
|
* @param string $filenameWithPath |
72
|
|
|
*/ |
73
|
51 |
|
public function __construct(string $filenameWithPath) |
74
|
|
|
{ |
75
|
51 |
|
$pathHelper = new PathHelper(); |
76
|
51 |
|
$this->filenameWithPath = $pathHelper->normalizePathSeparator($filenameWithPath); |
77
|
51 |
|
$filename = $pathHelper->extractFilenameFromPath($this->filenameWithPath); |
78
|
51 |
|
if (empty($filename) || false === $this->validatePath($this->filenameWithPath)) { |
79
|
2 |
|
throw new \RuntimeException( |
80
|
2 |
|
sprintf('Given path %s does not represents a file!', $filenameWithPath) |
81
|
|
|
); |
82
|
|
|
} |
83
|
49 |
|
list($this->name, $this->extension) = $pathHelper->splitFilename($filename); |
84
|
49 |
|
$this->hasExtension = (bool)$this->extension; |
85
|
49 |
|
$this->errorHandler = new ErrorToExceptionConverter(); |
86
|
49 |
|
} |
87
|
|
|
|
88
|
|
|
/** |
89
|
|
|
* States whether the file actually exists on disk |
90
|
|
|
* @return bool |
91
|
|
|
*/ |
92
|
50 |
|
public function exists(): bool |
|
|
|
|
93
|
|
|
{ |
94
|
50 |
|
return file_exists($this->filenameWithPath); |
95
|
|
|
} |
96
|
|
|
|
97
|
|
|
/** |
98
|
|
|
* States whether the file is readable |
99
|
|
|
* @return bool |
100
|
|
|
*/ |
101
|
9 |
|
public function isReadable(): bool |
102
|
|
|
{ |
103
|
9 |
|
return is_readable($this->filenameWithPath); |
104
|
|
|
} |
105
|
|
|
|
106
|
|
|
/** |
107
|
|
|
* @return bool |
108
|
|
|
*/ |
109
|
17 |
|
public function isWritable(): bool |
110
|
|
|
{ |
111
|
17 |
|
if ($this->exists()) { |
112
|
16 |
|
return is_writable($this->filenameWithPath); |
113
|
|
|
} |
114
|
1 |
|
$parts = explode(DIRECTORY_SEPARATOR, $this->filenameWithPath); |
115
|
1 |
|
array_pop($parts); |
116
|
1 |
|
return is_writable(implode(DIRECTORY_SEPARATOR, $parts)); |
117
|
|
|
} |
118
|
|
|
|
119
|
|
|
/** |
120
|
|
|
* Write content to the current file |
121
|
|
|
* |
122
|
|
|
* @param \NeedleProject\FileIo\Content\ContentInterface $content |
123
|
|
|
* @return \NeedleProject\FileIo\File |
124
|
|
|
* @throws \NeedleProject\FileIo\Exception\PermissionDeniedException |
125
|
|
|
*/ |
126
|
11 |
|
public function write(ContentInterface $content): File |
127
|
|
|
{ |
128
|
11 |
|
if ($this->isWritable() === false) { |
129
|
1 |
|
throw new PermissionDeniedException("The current file is not writable!"); |
130
|
|
|
} |
131
|
10 |
|
file_put_contents($this->filenameWithPath, $content->get()); |
132
|
10 |
|
return $this; |
133
|
|
|
} |
134
|
|
|
|
135
|
|
|
/** |
136
|
|
|
* @return \NeedleProject\FileIo\Content\ContentInterface |
137
|
|
|
* @throws \NeedleProject\FileIo\Exception\FileNotFoundException |
138
|
|
|
* @throws \NeedleProject\FileIo\Exception\IOException |
139
|
|
|
* @throws \NeedleProject\FileIo\Exception\PermissionDeniedException |
140
|
|
|
*/ |
141
|
9 |
|
public function getContent(): ContentInterface |
142
|
|
|
{ |
143
|
9 |
|
if ($this->exists() === false) { |
144
|
2 |
|
throw new FileNotFoundException(sprintf("%s does not exists!", $this->filenameWithPath)); |
145
|
|
|
} |
146
|
7 |
|
if ($this->isReadable() === false) { |
147
|
1 |
|
throw new PermissionDeniedException( |
148
|
1 |
|
sprintf("You do not have permissions to read file %s!", $this->filenameWithPath) |
149
|
|
|
); |
150
|
|
|
} |
151
|
6 |
|
$this->convertErrors(); |
152
|
6 |
|
$stringContent = file_get_contents($this->filenameWithPath); |
153
|
5 |
|
$this->resetErrorHandler(); |
154
|
5 |
|
if (false === $stringContent) { |
155
|
1 |
|
throw new IOException( |
156
|
1 |
|
sprintf("Could not retrieve content! Error message: %s", error_get_last()['message']) |
157
|
|
|
); |
158
|
|
|
} |
159
|
4 |
|
return $this->getContentFactory() |
160
|
4 |
|
->create($this->extension, $stringContent); |
161
|
|
|
} |
162
|
|
|
|
163
|
|
|
/** |
164
|
|
|
* Add content to the begging of the file |
165
|
|
|
* @param string $content |
166
|
|
|
* @return $this |
167
|
|
|
* @throws \NeedleProject\FileIo\Exception\PermissionDeniedException |
168
|
|
|
*/ |
169
|
4 |
View Code Duplication |
public function appendContent(string $content) |
|
|
|
|
170
|
|
|
{ |
171
|
4 |
|
if ($this->isWritable() === false) { |
172
|
1 |
|
throw new PermissionDeniedException("The current file is not writable!"); |
173
|
|
|
} |
174
|
3 |
|
file_put_contents($this->filenameWithPath, $content, FILE_APPEND); |
175
|
3 |
|
return $this; |
176
|
|
|
} |
177
|
|
|
|
178
|
|
|
/** |
179
|
|
|
* Add content to the begging of the file |
180
|
|
|
* @param string $content |
181
|
|
|
* @return $this |
182
|
|
|
* @throws \NeedleProject\FileIo\Exception\PermissionDeniedException |
183
|
|
|
*/ |
184
|
4 |
View Code Duplication |
public function prependContent(string $content) |
|
|
|
|
185
|
|
|
{ |
186
|
4 |
|
if ($this->isWritable() === false) { |
187
|
1 |
|
throw new PermissionDeniedException("The current file is not writable!"); |
188
|
|
|
} |
189
|
3 |
|
file_put_contents($this->filenameWithPath, $content . $this->getContent()->get()); |
190
|
3 |
|
return $this; |
191
|
|
|
} |
192
|
|
|
|
193
|
|
|
/** |
194
|
|
|
* Deletes the current file |
195
|
|
|
* @return bool |
196
|
|
|
* @throws \NeedleProject\FileIo\Exception\IOException |
197
|
|
|
*/ |
198
|
3 |
|
public function delete(): bool |
|
|
|
|
199
|
|
|
{ |
200
|
3 |
|
if ($this->exists() === false) { |
201
|
2 |
|
return false; |
202
|
|
|
} |
203
|
1 |
|
$this->convertErrors(); |
204
|
1 |
|
$unlinkResult = unlink($this->filenameWithPath); |
205
|
1 |
|
$this->resetErrorHandler(); |
206
|
1 |
|
return $unlinkResult; |
207
|
|
|
} |
208
|
|
|
|
209
|
|
|
/** |
210
|
|
|
* State existence of a file's extension |
211
|
|
|
* @return bool |
212
|
|
|
*/ |
213
|
10 |
|
public function hasExtension(): bool |
214
|
|
|
{ |
215
|
10 |
|
return $this->hasExtension; |
216
|
|
|
} |
217
|
|
|
|
218
|
|
|
/** |
219
|
|
|
* Get file's extension |
220
|
|
|
* @return string |
221
|
|
|
*/ |
222
|
4 |
|
public function getExtension(): string |
223
|
|
|
{ |
224
|
4 |
|
return $this->extension; |
225
|
|
|
} |
226
|
|
|
|
227
|
|
|
/** |
228
|
|
|
* Get file's name without extension |
229
|
|
|
* @return string |
230
|
|
|
*/ |
231
|
4 |
|
public function getName(): string |
232
|
|
|
{ |
233
|
4 |
|
return $this->name; |
234
|
|
|
} |
235
|
|
|
|
236
|
|
|
/** |
237
|
|
|
* Get file's name with extension |
238
|
|
|
* @return string |
239
|
|
|
*/ |
240
|
5 |
|
public function getBasename(): string |
241
|
|
|
{ |
242
|
5 |
|
if (false === $this->hasExtension()) { |
243
|
1 |
|
return $this->name; |
244
|
|
|
} |
245
|
4 |
|
return $this->name . static::EXTENSION_SEPARATOR . $this->extension; |
246
|
|
|
} |
247
|
|
|
|
248
|
|
|
/** |
249
|
|
|
* Returns a factory responsible for creating appropriate content |
250
|
|
|
* @return \NeedleProject\FileIo\Factory\ContentFactory |
251
|
|
|
*/ |
252
|
4 |
|
protected function getContentFactory(): ContentFactory |
253
|
|
|
{ |
254
|
4 |
|
if (is_null($this->contentFactory)) { |
255
|
4 |
|
$this->contentFactory = new ContentFactory(); |
256
|
|
|
} |
257
|
4 |
|
return $this->contentFactory; |
258
|
|
|
} |
259
|
|
|
|
260
|
|
|
/** |
261
|
|
|
* Validate if the given path is not a directory |
262
|
|
|
* @param string $filenameWithPath |
263
|
|
|
* @return bool |
264
|
|
|
*/ |
265
|
50 |
|
private function validatePath(string $filenameWithPath): bool |
|
|
|
|
266
|
|
|
{ |
267
|
50 |
|
return !($this->exists() && is_dir($filenameWithPath)); |
268
|
|
|
} |
269
|
|
|
|
270
|
|
|
/** |
271
|
|
|
* Convert errors to Exception type objects |
272
|
|
|
*/ |
273
|
7 |
|
protected function convertErrors() |
274
|
|
|
{ |
275
|
7 |
|
$this->errorHandler->convertErrorsToExceptions(E_ALL, IOException::class); |
276
|
7 |
|
} |
277
|
|
|
|
278
|
|
|
/** |
279
|
|
|
* Undo error to exception conversion |
280
|
|
|
*/ |
281
|
6 |
|
protected function resetErrorHandler() |
282
|
|
|
{ |
283
|
6 |
|
$this->errorHandler->restoreErrorHandler(); |
284
|
6 |
|
} |
285
|
|
|
} |
286
|
|
|
|
This check examines a number of code elements and verifies that they conform to the given naming conventions.
You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.