Completed
Push — master ( c26f50...f56ee5 )
by ignace nyamagana
04:19
created

HierarchicalPath::buildBasename()   A

Complexity

Conditions 4
Paths 8

Size

Total Lines 19
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 4

Importance

Changes 4
Bugs 0 Features 0
Metric Value
c 4
b 0
f 0
dl 0
loc 19
ccs 14
cts 14
cp 1
rs 9.2
nc 8
cc 4
eloc 11
nop 3
crap 4
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.2.0
10
 * @link      https://github.com/thephpleague/uri/
11
 */
12
namespace League\Uri\Components;
13
14
use InvalidArgumentException;
15
use League\Uri\Interfaces\HierarchicalComponent;
16
use League\Uri\Interfaces\HierarchicalPath as HierarchicalPathInterface;
17
18
/**
19
 * Value object representing a URI path component.
20
 *
21
 * @package League.uri
22
 * @author  Ignace Nyamagana Butera <[email protected]>
23
 * @since   1.0.0
24
 */
25
class HierarchicalPath extends AbstractHierarchicalComponent implements HierarchicalPathInterface
26
{
27
    use PathTrait;
28
29
    /**
30
     * @inheritdoc
31
     */
32
    protected static $separator = '/';
33
34
    /**
35
     * DEPRECATION WARNING! This method will be removed in the next major point release
36
     *
37
     * @deprecated deprecated since version 4.2
38
     *
39
     * return a new instance from an array or a traversable object
40
     *
41
     * @param \Traversable|string[] $data The segments list
42
     * @param int                   $type one of the constant IS_ABSOLUTE or IS_RELATIVE
43
     *
44
     * @throws InvalidArgumentException If $type is not a recognized constant
45
     *
46
     * @return static
47
     */
48
    public static function createFromArray($data, $type = self::IS_RELATIVE)
49
    {
50
        return static::createFromSegments($data, $type);
51
    }
52
53
   /**
54
     * return a new instance from an array or a traversable object
55
     *
56
     * @param \Traversable|string[] $data The segments list
57
     * @param int                   $type one of the constant IS_ABSOLUTE or IS_RELATIVE
58
     *
59
     * @throws InvalidArgumentException If $type is not a recognized constant
60
     *
61
     * @return static
62
     */
63 237
    public static function createFromSegments($data, $type = self::IS_RELATIVE)
64
    {
65 237
        static $type_list = [self::IS_ABSOLUTE => 1, self::IS_RELATIVE => 1];
66
67 237
        if (!isset($type_list[$type])) {
68 3
            throw new InvalidArgumentException('Please verify the submitted constant');
69
        }
70
71 234
        $path = implode(static::$separator, static::validateIterator($data));
72 222
        if (self::IS_ABSOLUTE == $type) {
73 171
            $path = static::$separator.$path;
74 114
        }
75
76 222
        return new static($path);
77
    }
78
79
    /**
80
     * New Instance
81
     *
82
     * @param string $path
83
     */
84 1009
    public function __construct($path = '')
85
    {
86 1009
        $path = $this->validateString($path);
87 1000
        $this->isAbsolute = self::IS_RELATIVE;
88 1000
        if (static::$separator == substr($path, 0, 1)) {
89 821
            $this->isAbsolute = self::IS_ABSOLUTE;
90 821
            $path = substr($path, 1, strlen($path));
91 547
        }
92
93 1000
        $append_delimiter = false;
94 1000
        if (static::$separator === substr($path, -1, 1)) {
95 138
            $path = substr($path, 0, -1);
96 138
            $append_delimiter = true;
97 92
        }
98
99 1000
        $this->data = $this->validate($path);
100 1000
        if ($append_delimiter) {
101 138
            $this->data[] = '';
102 92
        }
103 1000
    }
104
105
    /**
106
     * validate the submitted data
107
     *
108
     * @param string $data
109
     *
110
     * @return array
111
     */
112
    protected function validate($data)
113
    {
114 1000
        $filterSegment = function ($segment) {
115 1000
            return isset($segment);
116 1000
        };
117
118 1000
        $data = $this->decodePath($data);
119
120 1000
        return array_filter(explode(static::$separator, $data), $filterSegment);
121
    }
122
123
    /**
124
     * @inheritdoc
125
     */
126 9
    public static function __set_state(array $properties)
127
    {
128 9
        return static::createFromSegments($properties['data'], $properties['isAbsolute']);
129
    }
130
131
    /**
132
     * Return a new instance when needed
133
     *
134
     * @param array $data
135
     *
136
     * @return static
137
     */
138 63
    protected function newCollectionInstance(array $data)
139
    {
140 63
        return $this->createFromSegments($data, $this->isAbsolute);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->createFrom...ta, $this->isAbsolute); (League\Uri\Components\HierarchicalPath) is incompatible with the return type declared by the abstract method League\Uri\Components\Ab...::newCollectionInstance of type League\Uri\Types\ImmutableCollectionTrait.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
141
    }
142
143
    /**
144
     * Retrieves a single path segment.
145
     *
146
     * Retrieves a single path segment. If the segment offset has not been set,
147
     * returns the default value provided.
148
     *
149
     * @param string $offset  the segment offset
150
     * @param mixed  $default Default value to return if the offset does not exist.
151
     *
152
     * @return mixed
153
     */
154 9
    public function getSegment($offset, $default = null)
155
    {
156 9
        if (isset($this->data[$offset])) {
157 6
            return $this->data[$offset];
158
        }
159
160 3
        return $default;
161
    }
162
163
    /**
164
     * @inheritdoc
165
     */
166 967
    public function getContent()
167
    {
168 967
        $front_delimiter = '';
169 967
        if ($this->isAbsolute == self::IS_ABSOLUTE) {
170 779
            $front_delimiter = static::$separator;
171 519
        }
172
173 967
        return $this->encodePath(
174 967
            $front_delimiter.implode(static::$separator, $this->data)
175 644
        );
176
    }
177
178
    /**
179
     * @inheritdoc
180
     */
181 967
    public function __toString()
182
    {
183 967
        return (string) $this->getContent();
184
    }
185
186
    /**
187
     * @inheritdoc
188
     */
189 30
    public function prepend($component)
190
    {
191 30
        return $this->createFromSegments(
192 30
                $this->validateComponent($component),
193 30
                $this->isAbsolute
194 30
            )->append($this);
195
    }
196
197
    /**
198
     * Returns an instance with the specified component appended
199
     *
200
     * This method MUST retain the state of the current instance, and return
201
     * an instance that contains the modified component with the appended data
202
     *
203
     * @param HierarchicalComponent|string $component the component to append
204
     *
205
     * @return static
206
     */
207 72
    public function append($component)
208
    {
209 72
        $source = $this->toArray();
210 72
        if (!empty($source) && '' === end($source)) {
211 30
            array_pop($source);
212 20
        }
213
214 72
        return $this->createFromSegments(array_merge(
215 48
            $source,
216 72
            $this->validateComponent($component)->toArray()
217 72
        ), $this->isAbsolute);
218
    }
219
220
    /**
221
     * Returns the path basename
222
     *
223
     * @return string
224
     */
225 63
    public function getBasename()
226
    {
227 63
        $data = $this->data;
228
229 63
        return (string) array_pop($data);
230
    }
231
232
    /**
233
     * Returns parent directory's path
234
     *
235
     * @return string
236
     */
237 24
    public function getDirname()
238
    {
239 24
        return str_replace(
240 24
            ['\\', "\0"],
241 24
            [static::$separator, '\\'],
242 24
            dirname(str_replace('\\', "\0", $this->__toString()))
243 16
        );
244
    }
245
246
    /**
247
     * Returns the basename extension
248
     *
249
     * @return string
250
     */
251 57
    public function getExtension()
252
    {
253 57
        list($basename, ) = explode(';', $this->getBasename(), 2);
254
255 57
        return pathinfo($basename, PATHINFO_EXTENSION);
256
    }
257
258
    /**
259
     * Returns an instance with the specified basename extension
260
     *
261
     * This method MUST retain the state of the current instance, and return
262
     * an instance that contains the extension basename modified.
263
     *
264
     * @param string $extension the new extension
265
     *                          can preceeded with or without the dot (.) character
266
     *
267
     * @throws InvalidArgumentException If the extension is invalid
268
     *
269
     * @return static
270
     */
271 63
    public function withExtension($extension)
272
    {
273 63
        $extension = $this->formatExtension($extension);
274 57
        $segments = $this->toArray();
275 57
        $basename = array_pop($segments);
276 57
        $parts = explode(';', $basename, 2);
277 57
        $basenamePart = array_shift($parts);
278 57
        if ('' === $basenamePart || is_null($basenamePart)) {
279 6
            return $this;
280
        }
281
282 51
        $newBasename = $this->buildBasename($basenamePart, $extension, array_shift($parts));
283 51
        if ($basename === $newBasename) {
284 3
            return $this;
285
        }
286 48
        $segments[] = $newBasename;
287
288 48
        return $this->createFromSegments($segments, $this->isAbsolute);
289
    }
290
291
    /**
292
     * create a new basename with a new extension
293
     *
294
     * @param string $basenamePart  the basename file part
295
     * @param string $extension     the new extension to add
296
     * @param string $parameterPart the basename parameter part
297
     *
298
     * @return string
299
     */
300 51
    protected function buildBasename($basenamePart, $extension, $parameterPart)
301
    {
302 51
        $length = mb_strrpos($basenamePart, '.'.pathinfo($basenamePart, PATHINFO_EXTENSION), 'UTF-8');
303 51
        if (false !== $length) {
304 45
            $basenamePart = mb_substr($basenamePart, 0, $length, 'UTF-8');
305 30
        }
306
307 51
        $parameterPart = trim($parameterPart);
308 51
        if ('' !== $parameterPart) {
309 24
            $parameterPart = ";$parameterPart";
310 16
        }
311
312 51
        $extension = trim($extension);
313 51
        if ('' !== $extension) {
314 36
            $extension = ".$extension";
315 24
        }
316
317 51
        return $basenamePart.$extension.$parameterPart;
318
    }
319
320
    /**
321
     * validate and format the given extension
322
     *
323
     * @param string $extension the new extension to use
324
     *
325
     * @throws InvalidArgumentException If the extension is not valid
326
     *
327
     * @return string
328
     */
329 63
    protected function formatExtension($extension)
330
    {
331 63
        if (0 === strpos($extension, '.')) {
332 3
            throw new InvalidArgumentException('an extension sequence can not contain a leading `.` character');
333
        }
334
335 60
        if (strpos($extension, static::$separator)) {
336 3
            throw new InvalidArgumentException('an extension sequence can not contain a path delimiter');
337
        }
338
339 57
        return implode(static::$separator, $this->validate($extension));
340
    }
341
}
342