1
|
|
|
<?php |
2
|
|
|
namespace phootwork\file; |
3
|
|
|
|
4
|
|
|
use phootwork\lang\ArrayObject; |
5
|
|
|
use phootwork\lang\Text; |
6
|
|
|
|
7
|
|
|
class Path { |
8
|
|
|
|
9
|
|
|
/** @var ArrayObject */ |
10
|
|
|
private $segments; |
11
|
|
|
|
12
|
|
|
/** @var Text */ |
13
|
|
|
private $pathname; |
14
|
|
|
|
15
|
|
|
/** @var string */ |
16
|
|
|
private $dirname; |
17
|
|
|
|
18
|
|
|
/** @var string */ |
19
|
|
|
private $filename; |
20
|
|
|
|
21
|
|
|
/** @var string */ |
22
|
|
|
private $extension; |
23
|
|
|
|
24
|
|
|
|
25
|
14 |
|
public function __construct($pathname) { |
26
|
14 |
|
$this->init($pathname); |
27
|
14 |
|
} |
28
|
|
|
|
29
|
14 |
|
private function init($pathname) { |
30
|
14 |
|
$this->pathname = $pathname instanceof Text ? $pathname : new Text($pathname); |
31
|
14 |
|
$this->segments = $this->pathname->split('/'); |
32
|
14 |
|
$this->extension = pathinfo($this->pathname, PATHINFO_EXTENSION); |
33
|
14 |
|
$this->filename = basename($this->pathname); |
34
|
14 |
|
$this->dirname = dirname($this->pathname); |
35
|
14 |
|
} |
36
|
|
|
|
37
|
|
|
/** |
38
|
|
|
* Returns the extension |
39
|
|
|
* |
40
|
|
|
* @return string the extension |
41
|
|
|
*/ |
42
|
2 |
|
public function getExtension() { |
43
|
2 |
|
return $this->extension; |
44
|
|
|
} |
45
|
|
|
|
46
|
|
|
/** |
47
|
|
|
* Returns the filename |
48
|
|
|
* |
49
|
|
|
* @return string the filename |
50
|
|
|
*/ |
51
|
1 |
|
public function getFilename() { |
52
|
1 |
|
return $this->filename; |
53
|
|
|
} |
54
|
|
|
|
55
|
|
|
/** |
56
|
|
|
* Gets the path without filename |
57
|
|
|
* |
58
|
|
|
* @return string |
59
|
|
|
*/ |
60
|
5 |
|
public function getDirname() { |
61
|
5 |
|
return $this->dirname; |
62
|
|
|
} |
63
|
|
|
|
64
|
|
|
/** |
65
|
|
|
* Gets the full pathname |
66
|
|
|
* |
67
|
|
|
* @return Text |
68
|
|
|
*/ |
69
|
4 |
|
public function getPathname() { |
70
|
4 |
|
return $this->pathname; |
71
|
|
|
} |
72
|
|
|
|
73
|
|
|
/** |
74
|
|
|
* Changes the extension of this path |
75
|
|
|
* |
76
|
|
|
* @param string $extension the new extension |
77
|
|
|
* @return $this |
78
|
|
|
*/ |
79
|
1 |
|
public function setExtension($extension) { |
80
|
1 |
|
$pathinfo = pathinfo($this->pathname); |
81
|
|
|
|
82
|
1 |
|
$pathname = new Text($pathinfo['dirname']); |
83
|
1 |
|
if (!empty($pathinfo['dirname'])) { |
84
|
1 |
|
$pathname = $pathname->append('/'); |
85
|
|
|
} |
86
|
|
|
|
87
|
1 |
|
$this->init($pathname |
88
|
1 |
|
->append($pathinfo['filename']) |
89
|
1 |
|
->append('.') |
90
|
1 |
|
->append($extension)) |
91
|
|
|
; |
92
|
|
|
|
93
|
1 |
|
return $this; |
94
|
|
|
} |
95
|
|
|
|
96
|
|
|
/** |
97
|
|
|
* Returns a path with the same segments as this path but with a |
98
|
|
|
* trailing separator added (if not already existent). |
99
|
|
|
* |
100
|
|
|
* @return $this |
101
|
|
|
*/ |
102
|
3 |
|
public function addTrailingSeparator() { |
103
|
3 |
|
if (!$this->hasTrailingSeparator()) { |
104
|
3 |
|
$this->pathname = $this->pathname->append('/'); |
105
|
|
|
} |
106
|
3 |
|
return $this; |
107
|
|
|
} |
108
|
|
|
|
109
|
|
|
/** |
110
|
|
|
* Returns the path obtained from the concatenation of the given path's |
111
|
|
|
* segments/string to the end of this path. |
112
|
|
|
* |
113
|
|
|
* @param string|Text|Path $path |
114
|
|
|
* @return Path |
115
|
|
|
*/ |
116
|
2 |
|
public function append($path) { |
117
|
2 |
|
if ($path instanceof Path) { |
118
|
1 |
|
$path = $path->getPathname(); |
119
|
|
|
} |
120
|
|
|
|
121
|
2 |
|
if (!$this->hasTrailingSeparator()) { |
122
|
2 |
|
$this->addTrailingSeparator(); |
123
|
|
|
} |
124
|
|
|
|
125
|
2 |
|
return new Path($this->pathname->append($path)); |
126
|
|
|
} |
127
|
|
|
|
128
|
|
|
/** |
129
|
|
|
* Returns whether this path has a trailing separator. |
130
|
|
|
* |
131
|
|
|
* @return boolean |
132
|
|
|
*/ |
133
|
5 |
|
public function hasTrailingSeparator() { |
134
|
5 |
|
return $this->pathname->endsWith('/'); |
135
|
|
|
} |
136
|
|
|
|
137
|
|
|
/** |
138
|
|
|
* Returns whether this path is empty |
139
|
|
|
* |
140
|
|
|
* @return boolean |
141
|
|
|
*/ |
142
|
|
|
public function isEmpty() { |
143
|
|
|
return $this->pathname->isEmpty(); |
144
|
|
|
} |
145
|
|
|
|
146
|
|
|
/** |
147
|
|
|
* Returns whether this path is an absolute path. |
148
|
|
|
* |
149
|
|
|
* @return boolean |
150
|
|
|
*/ |
151
|
1 |
|
public function isAbsolute() { |
152
|
1 |
|
if (realpath($this->pathname->toString()) == $this->pathname->toString()) { |
153
|
1 |
|
return true; |
154
|
|
|
} |
155
|
|
|
|
156
|
1 |
|
if ($this->pathname->length() == 0 || $this->pathname->charAt(0) == '.') { |
|
|
|
|
157
|
1 |
|
return false; |
158
|
|
|
} |
159
|
|
|
|
160
|
|
|
// Windows allows absolute paths like this. |
161
|
1 |
|
if ($this->pathname->match('#^[a-zA-Z]:\\\\#')) { |
162
|
1 |
|
return true; |
163
|
|
|
} |
164
|
|
|
|
165
|
|
|
// A path starting with / or \ is absolute; anything else is relative. |
166
|
|
|
return $this->pathname->charAt(0) == '/' || $this->pathname->charAt(0) == '\\'; |
|
|
|
|
167
|
|
|
} |
168
|
|
|
|
169
|
|
|
/** |
170
|
|
|
* Checks whether this path is the prefix of another path |
171
|
|
|
* |
172
|
|
|
* @param Path $anotherPath |
173
|
|
|
* @return boolean |
174
|
|
|
*/ |
175
|
1 |
|
public function isPrefixOf(Path $anotherPath) { |
176
|
1 |
|
return $anotherPath->getPathname()->startsWith($this->pathname); |
177
|
|
|
} |
178
|
|
|
|
179
|
|
|
/** |
180
|
|
|
* Returns the last segment of this path, or null if it does not have any segments. |
181
|
|
|
* |
182
|
|
|
* @return Text |
183
|
|
|
*/ |
184
|
1 |
|
public function lastSegment() { |
185
|
1 |
|
return new Text($this->segments[count($this->segments) - 1]); |
186
|
|
|
} |
187
|
|
|
|
188
|
|
|
/** |
189
|
|
|
* Makes the path relative to another given path |
190
|
|
|
* |
191
|
|
|
* @param Path $base |
192
|
|
|
* @return Path the new relative path |
193
|
|
|
*/ |
194
|
2 |
|
public function makeRelativeTo(Path $base) { |
195
|
2 |
|
$pathname = clone $this->pathname; |
196
|
2 |
|
return new Path($pathname->replace($base->removeTrailingSeparator()->getPathname(), '')); |
197
|
|
|
} |
198
|
|
|
|
199
|
|
|
/** |
200
|
|
|
* Returns a count of the number of segments which match in this |
201
|
|
|
* path and the given path, comparing in increasing segment number order. |
202
|
|
|
* |
203
|
|
|
* @param Path $anotherPath |
204
|
|
|
* @return int |
205
|
|
|
*/ |
206
|
1 |
|
public function matchingFirstSegments(Path $anotherPath) { |
207
|
1 |
|
$segments = $anotherPath->segments(); |
208
|
1 |
|
$count = 0; |
209
|
1 |
|
foreach ($this->segments as $i => $segment) { |
210
|
1 |
|
if ($segment != $segments[$i]) { |
211
|
1 |
|
break; |
212
|
|
|
} |
213
|
1 |
|
$count++; |
214
|
|
|
} |
215
|
|
|
|
216
|
1 |
|
return $count; |
217
|
|
|
} |
218
|
|
|
|
219
|
|
|
/** |
220
|
|
|
* Returns a new path which is the same as this path but with the file extension removed. |
221
|
|
|
* |
222
|
|
|
* @return Path |
223
|
|
|
*/ |
224
|
1 |
|
public function removeExtension() { |
225
|
1 |
|
return new Path($this->pathname->replace('.' . $this->getExtension(), '')); |
226
|
|
|
} |
227
|
|
|
|
228
|
|
|
/** |
229
|
|
|
* Returns a copy of this path with the given number of segments removed from the beginning. |
230
|
|
|
* |
231
|
|
|
* @param int $count |
232
|
|
|
* @return Path |
233
|
|
|
*/ |
234
|
1 |
|
public function removeFirstSegments($count) { |
235
|
1 |
|
$segments = new ArrayObject(); |
236
|
1 |
|
for ($i = $count; $i < $this->segmentCount(); $i++) { |
237
|
1 |
|
$segments->push($this->segments[$i]); |
238
|
|
|
} |
239
|
1 |
|
return new Path($segments->join('/')); |
240
|
|
|
} |
241
|
|
|
|
242
|
|
|
/** |
243
|
|
|
* Returns a copy of this path with the given number of segments removed from the end. |
244
|
|
|
* |
245
|
|
|
* @param int $count |
246
|
|
|
* @return Path |
247
|
|
|
*/ |
248
|
1 |
|
public function removeLastSegments($count) { |
249
|
1 |
|
$segments = new ArrayObject(); |
250
|
1 |
|
for ($i = 0; $i < $this->segmentCount() - $count; $i++) { |
251
|
1 |
|
$segments->push($this->segments[$i]); |
252
|
|
|
} |
253
|
1 |
|
return new Path($segments->join('/')); |
254
|
|
|
} |
255
|
|
|
|
256
|
|
|
/** |
257
|
|
|
* Returns a copy of this path with the same segments as this path but with a trailing separator removed. |
258
|
|
|
* |
259
|
|
|
* @return $this |
260
|
|
|
*/ |
261
|
3 |
|
public function removeTrailingSeparator() { |
262
|
3 |
|
if ($this->hasTrailingSeparator()) { |
263
|
1 |
|
$this->pathname = $this->pathname->substring(0, -1); |
264
|
|
|
} |
265
|
3 |
|
return $this; |
266
|
|
|
} |
267
|
|
|
|
268
|
|
|
/** |
269
|
|
|
* Returns the specified segment of this path, or null if the path does not have such a segment. |
270
|
|
|
* |
271
|
|
|
* @param int $index |
272
|
|
|
* @return string |
273
|
|
|
*/ |
274
|
1 |
|
public function segment($index) { |
275
|
1 |
|
if (isset($this->segments[$index])) { |
276
|
1 |
|
return $this->segments[$index]; |
277
|
|
|
} |
278
|
|
|
|
279
|
1 |
|
return null; |
280
|
|
|
} |
281
|
|
|
|
282
|
|
|
/** |
283
|
|
|
* Returns the number of segments in this path. |
284
|
|
|
* |
285
|
|
|
* @return int |
286
|
|
|
*/ |
287
|
1 |
|
public function segmentCount() { |
288
|
1 |
|
return $this->segments->count(); |
289
|
|
|
} |
290
|
|
|
|
291
|
|
|
/** |
292
|
|
|
* Returns the segments in this path in order. |
293
|
|
|
* |
294
|
|
|
* @return ArrayObject<string> |
295
|
|
|
*/ |
296
|
2 |
|
public function segments() { |
297
|
2 |
|
return $this->segments; |
298
|
|
|
} |
299
|
|
|
|
300
|
|
|
/** |
301
|
|
|
* Returns a FileDescriptor corresponding to this path. |
302
|
|
|
* |
303
|
|
|
* @return FileDescriptor |
304
|
|
|
*/ |
305
|
2 |
|
public function toFileDescriptor() { |
306
|
2 |
|
return new FileDescriptor($this->pathname); |
307
|
|
|
} |
308
|
|
|
|
309
|
|
|
/** |
310
|
|
|
* Returns a string representation of this path |
311
|
|
|
* |
312
|
|
|
* @return string A string representation of this path |
313
|
|
|
*/ |
314
|
9 |
|
public function toString() { |
315
|
9 |
|
return $this->pathname->toString(); |
316
|
|
|
} |
317
|
|
|
|
318
|
|
|
/** |
319
|
|
|
* String representation as pathname |
320
|
|
|
*/ |
321
|
7 |
|
public function __toString() { |
322
|
7 |
|
return $this->toString(); |
323
|
|
|
} |
324
|
|
|
|
325
|
|
|
/** |
326
|
|
|
* Returns a copy of this path truncated after the given number of segments. |
327
|
|
|
* |
328
|
|
|
* @param int $count |
329
|
|
|
* @return Path |
330
|
|
|
*/ |
331
|
1 |
|
public function upToSegment($count) { |
332
|
1 |
|
$segments = new ArrayObject(); |
333
|
1 |
|
for ($i = 0; $i < $count; $i++) { |
334
|
1 |
|
$segments->push($this->segments[$i]); |
335
|
|
|
} |
336
|
|
|
|
337
|
1 |
|
return new Path($segments->join('/')); |
338
|
|
|
} |
339
|
|
|
|
340
|
|
|
/** |
341
|
|
|
* Checks whether both paths point to the same location |
342
|
|
|
* |
343
|
|
|
* @param Path|string $anotherPath |
344
|
|
|
* @return boolean true if the do, false if they don't |
345
|
|
|
*/ |
346
|
2 |
|
public function equals($anotherPath) { |
347
|
2 |
|
$anotherPath = $anotherPath instanceof Path ? $anotherPath : new Path($anotherPath); |
348
|
|
|
|
349
|
|
|
// do something else, when path's are urls |
350
|
2 |
|
$regexp = '/^[a-zA-Z]+:\/\//'; |
351
|
2 |
|
$thisUrl = $this->pathname->match($regexp); |
352
|
2 |
|
$anotherUrl = $anotherPath->getPathname()->match($regexp); |
353
|
|
|
|
354
|
2 |
|
if ($thisUrl ^ $anotherUrl) { |
355
|
1 |
|
return false; |
356
|
2 |
|
} else if ($thisUrl && $anotherUrl) { |
357
|
1 |
|
return $this->pathname->equals($anotherPath->getPathname()); |
358
|
|
|
} |
359
|
|
|
|
360
|
2 |
|
return realpath($this->pathname->toString()) == realpath($anotherPath->toString()); |
361
|
|
|
} |
362
|
|
|
|
363
|
|
|
} |
364
|
|
|
|
This method has been deprecated. The supplier of the class has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.