Completed
Push — master ( 977c56...7628a3 )
by ignace nyamagana
9s
created

PathTrait::withoutDotSegments()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 15
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 3

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 15
ccs 10
cts 10
cp 1
rs 9.4285
cc 3
eloc 9
nc 3
nop 0
crap 3
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.0
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
     * Returns an instance without dot segments
77
     *
78
     * This method MUST retain the state of the current instance, and return
79
     * an instance that contains the path component normalized by removing
80
     * the dot segment.
81
     *
82
     * @return static
83
     */
84 165
    public function withoutDotSegments()
85
    {
86 165
        $current = $this->__toString();
87 165
        if (false === strpos($current, '.')) {
88 84
            return $this;
89
        }
90
91 84
        $input = explode('/', $current);
92 84
        $new   = implode('/', array_reduce($input, [$this, 'filterDotSegments'], []));
93 84
        if (isset(static::$dotSegments[end($input)])) {
94 27
            $new .= '/';
95 18
        }
96
97 84
        return $this->modify($new);
98
    }
99
100
    /**
101
     * Filter Dot segment according to RFC3986
102
     *
103
     * @see http://tools.ietf.org/html/rfc3986#section-5.2.4
104
     *
105
     * @param array  $carry   Path segments
106
     * @param string $segment a path segment
107
     *
108
     * @return array
109
     */
110 84
    protected function filterDotSegments(array $carry, $segment)
111
    {
112 84
        if ('..' == $segment) {
113 57
            array_pop($carry);
114
115 57
            return $carry;
116
        }
117
118 84
        if (!isset(static::$dotSegments[$segment])) {
119 84
            $carry[] = $segment;
120 56
        }
121
122 84
        return $carry;
123
    }
124
125
    /**
126
     * Returns an instance without duplicate delimiters
127
     *
128
     * This method MUST retain the state of the current instance, and return
129
     * an instance that contains the path component normalized by removing
130
     * multiple consecutive empty segment
131
     *
132
     * @return static
133
     */
134 15
    public function withoutEmptySegments()
135
    {
136 15
        return $this->modify(preg_replace(',/+,', '/', $this->__toString()));
137
    }
138
139
    /**
140
     * Returns whether or not the path has a trailing delimiter
141
     *
142
     * @return bool
143
     */
144 60
    public function hasTrailingSlash()
145
    {
146 60
        $path = $this->__toString();
147
148 60
        return '' !== $path && '/' === mb_substr($path, -1, 1, 'UTF-8');
149
    }
150
151
    /**
152
     * Returns an instance with a trailing slash
153
     *
154
     * This method MUST retain the state of the current instance, and return
155
     * an instance that contains the path component with a trailing slash
156
     *
157
     *
158
     * @return static
159
     */
160 21
    public function withTrailingSlash()
161
    {
162 21
        return $this->hasTrailingSlash() ? $this : $this->modify($this->__toString().'/');
163
    }
164
165
    /**
166
     * Returns an instance without a trailing slash
167
     *
168
     * This method MUST retain the state of the current instance, and return
169
     * an instance that contains the path component without a trailing slash
170
     *
171
     * @return static
172
     */
173 21
    public function withoutTrailingSlash()
174
    {
175 21
        return !$this->hasTrailingSlash() ? $this : $this->modify(mb_substr($this->__toString(), 0, -1, 'UTF-8'));
176
    }
177
178
    /**
179
     * Returns whether or not the path is absolute or relative
180
     *
181
     * @return bool
182
     */
183 153
    public function isAbsolute()
184
    {
185 153
        $path = $this->__toString();
186
187 153
        return '' !== $path && '/' === mb_substr($path, 0, 1, 'UTF-8');
188
    }
189
190
    /**
191
     * Returns an instance with a leading slash
192
     *
193
     * This method MUST retain the state of the current instance, and return
194
     * an instance that contains the path component with a leading slash
195
     *
196
     *
197
     * @return static
198
     */
199 24
    public function withLeadingSlash()
200
    {
201 24
        return $this->isAbsolute() ? $this : $this->modify('/'.$this->__toString());
202
    }
203
204
    /**
205
     * Returns an instance without a leading slash
206
     *
207
     * This method MUST retain the state of the current instance, and return
208
     * an instance that contains the path component without a leading slash
209
     *
210
     * @return static
211
     */
212 21
    public function withoutLeadingSlash()
213
    {
214 21
        return !$this->isAbsolute() ? $this : $this->modify(mb_substr($this->__toString(), 1, null, 'UTF-8'));
215
    }
216
217
    /**
218
     * Retrieve the optional type associated to the path.
219
     *
220
     * The value returned MUST be one of the interface constant type
221
     * If no type is associated the return constant must be self::FTP_TYPE_EMPTY
222
     *
223
     * @see http://tools.ietf.org/html/rfc1738#section-3.2.2
224
     *
225
     * @return int a typecode constant.
226
     */
227 18
    public function getTypecode()
228
    {
229 18
        if (preg_match(self::$typeRegex, $this->__toString(), $matches)) {
230 9
            return self::$typecodeList[$matches['typecode']];
231
        }
232
233 9
        return PathInterface::FTP_TYPE_EMPTY;
234
    }
235
236
    /**
237
     * Return an instance with the specified typecode.
238
     *
239
     * This method MUST retain the state of the current instance, and return
240
     * an instance that contains the specified type appended to the path.
241
     * if not
242
     *
243
     * Using self::FTP_TYPE_EMPTY is equivalent to removing the typecode.
244
     *
245
     * @param int $type one typecode constant.
246
     *
247
     * @throws InvalidArgumentException for invalid typecode.
248
     *
249
     * @return static
250
     *
251
     */
252 30
    public function withTypecode($type)
253
    {
254 30
        if (!in_array($type, self::$typecodeList)) {
255 3
            throw new InvalidArgumentException('invalid typecode');
256
        }
257
258 27
        $path = $this->__toString();
259 27
        if (preg_match(self::$typeRegex, $path, $matches)) {
260 6
            $path = $matches['basename'];
261 4
        }
262
263 27
        $extension = array_search($type, self::$typecodeList);
264 27
        $extension = trim($extension);
265 27
        if ('' !== $extension) {
266 15
            $extension = ';type='.$extension;
267 10
        }
268
269 27
        return $this->modify($path.$extension);
270
    }
271
272
    /**
273
     * Encode a path string according to RFC3986
274
     *
275
     * @param string $subject can be a string or an array
276
     *
277
     * @return string The same type as the input parameter
278
     */
279 1101
    protected static function encodePath($subject)
280
    {
281
        $encoder = function (array $matches) {
282 957
            return rawurlencode($matches[0]);
283 1101
        };
284
285
        $formatter = function (array $matches) {
286 90
            return strtoupper($matches['encode']);
287 1101
        };
288
289 1101
        $subject = preg_replace_callback(self::$pathReservedCharactersRegex, $encoder, $subject);
290
291 1101
        return preg_replace_callback(',(?<encode>%[0-9a-f]{2}),', $formatter, $subject);
292
    }
293
294
    /**
295
     * Decode a path string according to RFC3986
296
     *
297
     * @param string $subject can be a string or an array
298
     *
299
     * @return string The same type as the input parameter
300
     */
301
    protected static function decodePath($subject)
302
    {
303 1086
        $decoder = function (array $matches) {
304 939
            return rawurldecode($matches[0]);
305 1086
        };
306
307 1086
        return preg_replace_callback(self::$pathReservedCharactersRegex, $decoder, $subject);
308
    }
309
}
310