Passed
Push — master ( 864d7f...a4d677 )
by Sebastian
02:02
created

Base::normalizeStream()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 10
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 3

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 7
c 1
b 0
f 0
nc 2
nop 1
dl 0
loc 10
rs 10
ccs 8
cts 8
cp 1
crap 3
1
<?php
2
/**
3
 * This file is part of Camino.
4
 *
5
 * (c) Sebastian Feldmann <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
namespace SebastianFeldmann\Camino\Path;
11
12
use RuntimeException;
13
use SebastianFeldmann\Camino\Path;
14
15
/**
16
 * Base path class for files and directories
17
 *
18
 * @package SebastianFeldmann\Camino
19
 */
20
abstract class Base implements Path
21
{
22
    /**
23
     * The originally given path
24
     *
25
     * @var string
26
     */
27
    protected $raw;
28
29
    /**
30
     * The path without the root (/, C:/, stream://)
31
     *
32
     * @var string
33
     */
34
    protected $path;
35
36
    /**
37
     * Amount of path segments
38
     *
39
     * @var int
40
     */
41
    protected $depth;
42
43
    /**
44
     * List of path segments
45
     *
46
     * @var string[]
47
     */
48
    protected $segments;
49
50
    /**
51
     * The path root (/, C:/, stream://)
52
     *
53
     * @var string
54
     */
55
    protected $root;
56
57
    /**
58
     * Absolute constructor.
59
     *
60
     * @param string $path
61
     */
62 15
    public function __construct(string $path)
63
    {
64 15
        $this->raw = $path;
65 15
        $this->normalize($path);
66 14
    }
67
68
    /**
69
     * Normalize a path detect root and segments
70
     *
71
     * @param string $path
72
     */
73 15
    private function normalize(string $path): void
74
    {
75
        // absolute linux|unix path
76 15
        if (substr($path, 0, 1) === '/') {
77 10
            $this->root = '/';
78 10
            $this->path = ltrim($path, '/');
79 10
            $this->detectSegments($this->path);
80 10
            return;
81
        }
82
83
        // check windows path
84 5
        if ($this->normalizeWindows($path)) {
85 3
            return;
86
        }
87
88
        // check streams
89 2
        if ($this->normalizeStream($path)) {
90 1
            return;
91
        }
92
93 1
        throw new RuntimeException('path must be absolute');
94
    }
95
96
    /**
97
     * Normalize a windows path
98
     *
99
     * @param  string $path
100
     * @return bool
101
     */
102 5
    private function normalizeWindows(string $path): bool
103
    {
104
        // check for C:\ or C:/
105 5
        $driveMatch = [];
106 5
        if (strlen($path) >= 3 && preg_match('#^([A-Z]\:)[/\\\]#i', substr($path, 0, 3), $driveMatch)) {
107 3
            $this->root = $driveMatch[1];
108 3
            $path       = substr($path, 2);
109
        }
110
111 5
        if (substr($path, 0, 1) === '\\') {
112 3
            $this->root = trim($this->root, '/\\') . '/';
113 3
            $this->path = trim(str_replace('\\', '/', $path), '/');
114 3
            $this->detectSegments($this->path);
115 3
            return true;
116
        }
117
118 2
        return false;
119
    }
120
121
    /**
122
     * Normalize a stream path
123
     *
124
     * @param  string $path
125
     * @return bool
126
     */
127 2
    private function normalizeStream(string $path): bool
128
    {
129 2
        $schemeMatch = [];
130 2
        if (strlen($path) > 4 && preg_match('#^([A-Z]+\://).#i', $path, $schemeMatch)) {
131 1
            $this->root = $schemeMatch[1];
132 1
            $this->path = substr($path, strlen($this->root));
133 1
            $this->detectSegments($this->path);
134 1
            return true;
135
        }
136 1
        return false;
137
    }
138
139
    /**
140
     * Detect all path segments
141
     *
142
     * @param string $path
143
     */
144 14
    private function detectSegments(string $path)
145
    {
146 14
        $this->segments = empty($path) ? [] : explode('/', trim($path, '/'));
147 14
        $this->depth    = count($this->segments);
148 14
    }
149
150
    /**
151
     * Path getter
152
     *
153
     * @return string
154
     */
155 6
    public function getPath(): string
156
    {
157 6
        return $this->raw;
158
    }
159
160
    /**
161
     * Root getter
162
     *
163
     * @return string
164
     */
165 6
    public function getRoot(): string
166
    {
167 6
        return $this->root;
168
    }
169
170
    /**
171
     * Depth getter
172
     *
173
     * @return int
174
     */
175 9
    public function getDepth(): int
176
    {
177 9
        return $this->depth;
178
    }
179
180
    /**
181
     * Segments getter
182
     *
183
     * @return array
184
     */
185 7
    public function getSegments(): array
186
    {
187 7
        return $this->segments;
188
    }
189
190
    /**
191
     * Check if a path is child of a given parent path
192
     *
193
     * @param \SebastianFeldmann\Camino\Path\Directory $parent
194
     * @return bool
195
     */
196 6
    public function isChildOf(Directory $parent): bool
197
    {
198 6
        if (!$this->isPossibleParent($parent)) {
199 2
            return false;
200
        }
201
202
        // check every path segment of the parent
203 5
        foreach ($parent->getSegments() as $index => $name) {
204 5
            if ($this->segments[$index] !== $name) {
205 5
                return false;
206
            }
207
        }
208 3
        return true;
209
    }
210
211
    /**
212
     * Returns the relative path from a parent directory to this one
213
     *
214
     * @param  \SebastianFeldmann\Camino\Path\Directory $parent
215
     * @return string
216
     */
217 2
    public function getRelativePathFrom(Directory $parent): string
218
    {
219 2
        if (!$this->isChildOf($parent)) {
220 1
            throw new RuntimeException($this->getPath() . ' is not a child of ' . $parent->getPath());
221
        }
222 1
        return implode('/', array_slice($this->segments, $parent->getDepth()));
223
    }
224
225
    /**
226
     * Check if a Directory possibly be a parent directory
227
     *
228
     * @param  \SebastianFeldmann\Camino\Path\Directory $parent
229
     * @return bool
230
     */
231 6
    protected function isPossibleParent(Directory $parent): bool
232
    {
233
234
        // if the root is different it can't be a subdirectory
235 6
        if (!$this->hasSameRootAs($parent)) {
236 1
            return false;
237
        }
238
        // if the parent has a deeper nesting level it can't be a parent
239 5
        if ($parent->getDepth() > $this->getDepth()) {
240 1
            return false;
241
        }
242 5
        return true;
243
    }
244
245
    /**
246
     * Check if a given path has the same root
247
     *
248
     * @param  \SebastianFeldmann\Camino\Path $path
249
     * @return bool
250
     */
251 6
    protected function hasSameRootAs(Path $path): bool
252
    {
253 6
        return $this->root === $path->getRoot();
254
    }
255
256
    /**
257
     * To string conversion method
258
     *
259
     * @return string
260
     */
261 1
    public function __toString(): string
262
    {
263 1
        return $this->raw;
264
    }
265
}
266
267
268