Base::normalizeWindows()   A
last analyzed

Complexity

Conditions 5
Paths 8

Size

Total Lines 22
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 5

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 5
eloc 12
c 1
b 0
f 0
nc 8
nop 1
dl 0
loc 22
rs 9.5555
ccs 13
cts 13
cp 1
crap 5
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 18
    public function __construct(string $path)
63
    {
64 18
        $this->raw = $path;
65 18
        $this->normalize($path);
66 17
    }
67
68
    /**
69
     * Normalize a path detect root and segments
70
     *
71
     * @param string $path
72
     */
73 18
    private function normalize(string $path): void
74
    {
75
        // absolute linux|unix path
76 18
        if (substr($path, 0, 1) === '/') {
77 12
            $this->root = '/';
78 12
            $this->path = ltrim($path, '/');
79 12
            $this->detectSegments($this->path);
80 12
            return;
81
        }
82
83
        // check streams
84 6
        if ($this->normalizeStream($path)) {
85 1
            return;
86
        }
87
88
        // check windows path
89 5
        if ($this->normalizeWindows($path)) {
90 4
            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 4
            $this->root = $driveMatch[1];
108 4
            $path       = substr($path, 2);
109
        }
110
111
        // normalize \ to /
112 5
        if (substr($path, 0, 1) === '\\') {
113 3
            $path = str_replace('\\', '/', $path);
114
        }
115
116 5
        if (substr($path, 0, 1) === '/') {
117 4
            $this->root = trim($this->root, '/\\') . '/';
118 4
            $this->path = trim($path, '/');
119 4
            $this->detectSegments($this->path);
120 4
            return true;
121
        }
122
123 1
        return false;
124
    }
125
126
    /**
127
     * Normalize a stream path
128
     *
129
     * @param  string $path
130
     * @return bool
131
     */
132 6
    private function normalizeStream(string $path): bool
133
    {
134 6
        $schemeMatch = [];
135 6
        if (strlen($path) > 4 && preg_match('#^([A-Z]+\://).#i', $path, $schemeMatch)) {
136 1
            $this->root = $schemeMatch[1];
137 1
            $this->path = substr($path, strlen($this->root));
138 1
            $this->detectSegments($this->path);
139 1
            return true;
140
        }
141 5
        return false;
142
    }
143
144
    /**
145
     * Detect all path segments
146
     *
147
     * @param string $path
148
     */
149 17
    private function detectSegments(string $path)
150
    {
151 17
        $this->segments = empty($path) ? [] : explode('/', trim($path, '/'));
152 17
        $this->depth    = count($this->segments);
153 17
    }
154
155
    /**
156
     * Path getter
157
     *
158
     * @return string
159
     */
160 9
    public function getPath(): string
161
    {
162 9
        return $this->raw;
163
    }
164
165
    /**
166
     * Root getter
167
     *
168
     * @return string
169
     */
170 6
    public function getRoot(): string
171
    {
172 6
        return $this->root;
173
    }
174
175
    /**
176
     * Depth getter
177
     *
178
     * @return int
179
     */
180 10
    public function getDepth(): int
181
    {
182 10
        return $this->depth;
183
    }
184
185
    /**
186
     * Segments getter
187
     *
188
     * @return array
189
     */
190 7
    public function getSegments(): array
191
    {
192 7
        return $this->segments;
193
    }
194
195
    /**
196
     * Check if a path is child of a given parent path
197
     *
198
     * @param \SebastianFeldmann\Camino\Path\Directory $parent
199
     * @return bool
200
     */
201 6
    public function isChildOf(Directory $parent): bool
202
    {
203 6
        if (!$this->isPossibleParent($parent)) {
204 2
            return false;
205
        }
206
207
        // check every path segment of the parent
208 5
        foreach ($parent->getSegments() as $index => $name) {
209 5
            if ($this->segments[$index] !== $name) {
210 3
                return false;
211
            }
212
        }
213 3
        return true;
214
    }
215
216
    /**
217
     * Returns the relative path from a parent directory to this one
218
     *
219
     * @param  \SebastianFeldmann\Camino\Path\Directory $parent
220
     * @return string
221
     */
222 2
    public function getRelativePathFrom(Directory $parent): string
223
    {
224 2
        if (!$this->isChildOf($parent)) {
225 1
            throw new RuntimeException($this->getPath() . ' is not a child of ' . $parent->getPath());
226
        }
227 1
        return implode('/', array_slice($this->segments, $parent->getDepth()));
228
    }
229
230
    /**
231
     * Check if a Directory possibly be a parent directory
232
     *
233
     * @param  \SebastianFeldmann\Camino\Path\Directory $parent
234
     * @return bool
235
     */
236 6
    protected function isPossibleParent(Directory $parent): bool
237
    {
238
239
        // if the root is different it can't be a subdirectory
240 6
        if (!$this->hasSameRootAs($parent)) {
241 1
            return false;
242
        }
243
        // if the parent has a deeper nesting level it can't be a parent
244 5
        if ($parent->getDepth() > $this->getDepth()) {
245 1
            return false;
246
        }
247 5
        return true;
248
    }
249
250
    /**
251
     * Check if a given path has the same root
252
     *
253
     * @param  \SebastianFeldmann\Camino\Path $path
254
     * @return bool
255
     */
256 6
    protected function hasSameRootAs(Path $path): bool
257
    {
258 6
        return $this->root === $path->getRoot();
259
    }
260
261
    /**
262
     * To string conversion method
263
     *
264
     * @return string
265
     */
266 1
    public function __toString(): string
267
    {
268 1
        return $this->raw;
269
    }
270
}
271
272
273