Completed
Push — master ( 0cbce9...ce80be )
by ignace nyamagana
8s
created

PathTrait::isAbsolute()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 6
ccs 3
cts 3
cp 1
rs 9.4285
cc 2
eloc 3
nc 2
nop 0
crap 2
1
<?php
2
/**
3
 * League.Uri (http://uri.thephpleague.com)
4
 *
5
 * @package   League.uri
6
 * @author    Ignace Nyamagana Butera <[email protected]>
7
 * @copyright 2013-2015 Ignace Nyamagana Butera
8
 * @license   https://github.com/thephpleague/uri/blob/master/LICENSE (MIT License)
9
 * @version   4.1.1
10
 * @link      https://github.com/thephpleague/uri/
11
 */
12
namespace League\Uri\Components;
13
14
use InvalidArgumentException;
15
use League\Uri\Interfaces\Path as PathInterface;
16
17
/**
18
 * Value object representing a URI path component.
19
 *
20
 * @package League.uri
21
 * @author  Ignace Nyamagana Butera <[email protected]>
22
 * @since   4.0.0
23
 */
24
trait PathTrait
25
{
26
    /**
27
     * Typecode Regular expression
28
     */
29
    protected static $typeRegex = ',^(?P<basename>.*);type=(?P<typecode>a|i|d)$,';
30
31
    /**
32
     * Paht reserved characters regular expression
33
     */
34
    protected static $pathReservedCharactersRegex = "/(?:[^\!\$&'\(\)\*\+,;\=\:\/@\?%]+|%(?![A-Fa-f0-9]{2}))/S";
35
36
    /**
37
     * Typecode value
38
     *
39
     * @var array
40
     */
41
    protected static $typecodeList = [
42
        'a' => PathInterface::FTP_TYPE_ASCII,
43
        'i' => PathInterface::FTP_TYPE_BINARY,
44
        'd' => PathInterface::FTP_TYPE_DIRECTORY,
45
        ''  => PathInterface::FTP_TYPE_EMPTY,
46
    ];
47
48
    /**
49
     * Dot Segment pattern
50
     *
51
     * @var array
52
     */
53
    protected static $dotSegments = ['.' => 1, '..' => 1];
54
55
    /**
56
     * Returns the instance string representation; If the
57
     * instance is not defined an empty string is returned
58
     *
59
     * @return string
60
     */
61
    abstract public function __toString();
62
63
    /**
64
     * Returns an instance with the specified string
65
     *
66
     * This method MUST retain the state of the current instance, and return
67
     * an instance that contains the modified data
68
     *
69
     * @param string $value
70
     *
71
     * @return static
72
     */
73
    abstract public function modify($value);
74
75
    /**
76
     * @inheritdoc
77
     */
78 6
    public function __debugInfo()
79
    {
80 6
        return ['path' => $this->__toString()];
81
    }
82
83
    /**
84
     * Returns an instance without dot segments
85
     *
86
     * This method MUST retain the state of the current instance, and return
87
     * an instance that contains the path component normalized by removing
88
     * the dot segment.
89
     *
90
     * @return static
91
     */
92 165
    public function withoutDotSegments()
93
    {
94 165
        $current = $this->__toString();
95 165
        if (false === strpos($current, '.')) {
96 84
            return $this;
97
        }
98
99 84
        $input = explode('/', $current);
100 84
        $new   = implode('/', array_reduce($input, [$this, 'filterDotSegments'], []));
101 84
        if (isset(static::$dotSegments[end($input)])) {
102 27
            $new .= '/';
103 18
        }
104
105 84
        return $this->modify($new);
106
    }
107
108
    /**
109
     * Filter Dot segment according to RFC3986
110
     *
111
     * @see http://tools.ietf.org/html/rfc3986#section-5.2.4
112
     *
113
     * @param array  $carry   Path segments
114
     * @param string $segment a path segment
115
     *
116
     * @return array
117
     */
118 84
    protected function filterDotSegments(array $carry, $segment)
119
    {
120 84
        if ('..' == $segment) {
121 57
            array_pop($carry);
122
123 57
            return $carry;
124
        }
125
126 84
        if (!isset(static::$dotSegments[$segment])) {
127 84
            $carry[] = $segment;
128 56
        }
129
130 84
        return $carry;
131
    }
132
133
    /**
134
     * Returns an instance without duplicate delimiters
135
     *
136
     * This method MUST retain the state of the current instance, and return
137
     * an instance that contains the path component normalized by removing
138
     * multiple consecutive empty segment
139
     *
140
     * @return static
141
     */
142 15
    public function withoutEmptySegments()
143
    {
144 15
        return $this->modify(preg_replace(',/+,', '/', $this->__toString()));
145
    }
146
147
    /**
148
     * Returns whether or not the path has a trailing delimiter
149
     *
150
     * @return bool
151
     */
152 60
    public function hasTrailingSlash()
153
    {
154 60
        $path = $this->__toString();
155
156 60
        return '' !== $path && '/' === mb_substr($path, -1, 1, 'UTF-8');
157
    }
158
159
    /**
160
     * Returns an instance with a trailing slash
161
     *
162
     * This method MUST retain the state of the current instance, and return
163
     * an instance that contains the path component with a trailing slash
164
     *
165
     *
166
     * @return static
167
     */
168 21
    public function withTrailingSlash()
169
    {
170 21
        return $this->hasTrailingSlash() ? $this : $this->modify($this->__toString().'/');
171
    }
172
173
    /**
174
     * Returns an instance without a trailing slash
175
     *
176
     * This method MUST retain the state of the current instance, and return
177
     * an instance that contains the path component without a trailing slash
178
     *
179
     * @return static
180
     */
181 21
    public function withoutTrailingSlash()
182
    {
183 21
        return !$this->hasTrailingSlash() ? $this : $this->modify(mb_substr($this->__toString(), 0, -1, 'UTF-8'));
184
    }
185
186
    /**
187
     * Returns whether or not the path is absolute or relative
188
     *
189
     * @return bool
190
     */
191 153
    public function isAbsolute()
192
    {
193 153
        $path = $this->__toString();
194
195 153
        return '' !== $path && '/' === mb_substr($path, 0, 1, 'UTF-8');
196
    }
197
198
    /**
199
     * Returns an instance with a leading slash
200
     *
201
     * This method MUST retain the state of the current instance, and return
202
     * an instance that contains the path component with a leading slash
203
     *
204
     *
205
     * @return static
206
     */
207 24
    public function withLeadingSlash()
208
    {
209 24
        return $this->isAbsolute() ? $this : $this->modify('/'.$this->__toString());
210
    }
211
212
    /**
213
     * Returns an instance without a leading slash
214
     *
215
     * This method MUST retain the state of the current instance, and return
216
     * an instance that contains the path component without a leading slash
217
     *
218
     * @return static
219
     */
220 21
    public function withoutLeadingSlash()
221
    {
222 21
        return !$this->isAbsolute() ? $this : $this->modify(mb_substr($this->__toString(), 1, null, 'UTF-8'));
223
    }
224
225
    /**
226
     * Retrieve the optional type associated to the path.
227
     *
228
     * The value returned MUST be one of the interface constant type
229
     * If no type is associated the return constant must be self::FTP_TYPE_EMPTY
230
     *
231
     * @see http://tools.ietf.org/html/rfc1738#section-3.2.2
232
     *
233
     * @return int a typecode constant.
234
     */
235 18
    public function getTypecode()
236
    {
237 18
        if (preg_match(self::$typeRegex, $this->__toString(), $matches)) {
238 9
            return self::$typecodeList[$matches['typecode']];
239
        }
240
241 9
        return PathInterface::FTP_TYPE_EMPTY;
242
    }
243
244
    /**
245
     * Return an instance with the specified typecode.
246
     *
247
     * This method MUST retain the state of the current instance, and return
248
     * an instance that contains the specified type appended to the path.
249
     * if not
250
     *
251
     * Using self::FTP_TYPE_EMPTY is equivalent to removing the typecode.
252
     *
253
     * @param int $type one typecode constant.
254
     *
255
     * @throws InvalidArgumentException for invalid typecode.
256
     *
257
     * @return static
258
     *
259
     */
260 30
    public function withTypecode($type)
261
    {
262 30
        if (!in_array($type, self::$typecodeList)) {
263 3
            throw new InvalidArgumentException('invalid typecode');
264
        }
265
266 27
        $path = $this->__toString();
267 27
        if (preg_match(self::$typeRegex, $path, $matches)) {
268 6
            $path = $matches['basename'];
269 4
        }
270
271 27
        $extension = array_search($type, self::$typecodeList);
272 27
        $extension = trim($extension);
273 27
        if ('' !== $extension) {
274 15
            $extension = ';type='.$extension;
275 10
        }
276
277 27
        return $this->modify($path.$extension);
278
    }
279
280
    /**
281
     * Encode a path string according to RFC3986
282
     *
283
     * @param string $subject can be a string or an array
284
     *
285
     * @return string The same type as the input parameter
286
     */
287 1121
    protected static function encodePath($subject)
288
    {
289
        $encoder = function (array $matches) {
290 977
            return rawurlencode($matches[0]);
291 1121
        };
292
293
        $formatter = function (array $matches) {
294 90
            return strtoupper($matches['encode']);
295 1121
        };
296
297 1121
        $subject = preg_replace_callback(self::$pathReservedCharactersRegex, $encoder, $subject);
298
299 1121
        return preg_replace_callback(',(?<encode>%[0-9a-f]{2}),', $formatter, $subject);
300
    }
301
302
    /**
303
     * Decode a path string according to RFC3986
304
     *
305
     * @param string $subject can be a string or an array
306
     *
307
     * @return string The same type as the input parameter
308
     */
309
    protected static function decodePath($subject)
310
    {
311 1101
        $decoder = function (array $matches) {
312 954
            return rawurldecode($matches[0]);
313 1101
        };
314
315 1101
        return preg_replace_callback(self::$pathReservedCharactersRegex, $decoder, $subject);
316
    }
317
}
318