Completed
Push — master ( 852949...4d8337 )
by Thomas
10s
created

Path::isAbsolute()   B

Complexity

Conditions 7
Paths 6

Size

Total Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 7.049

Importance

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