Completed
Push — master ( 5a1f4e...aa9408 )
by ignace nyamagana
12:29
created

AbstractHierarchicalComponent::withContent()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 1
dl 0
loc 8
ccs 4
cts 4
cp 1
crap 2
rs 9.4285
c 0
b 0
f 0
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\Types\ImmutableCollectionTrait;
17
use League\Uri\Types\ImmutableComponentTrait;
18
19
/**
20
 * An abstract class to ease collection like Component object manipulation
21
 *
22
 * @package League.uri
23
 * @author  Ignace Nyamagana Butera <[email protected]>
24
 * @since   4.0.0
25
 */
26
abstract class AbstractHierarchicalComponent
27
{
28
    use ImmutableCollectionTrait;
29
30
    use ImmutableComponentTrait;
31
32
    const IS_ABSOLUTE = 1;
33
34
    const IS_RELATIVE = 0;
35
36
    /**
37
     * Hierarchical component separator
38
     *
39
     * @var string
40
     */
41
    protected static $separator;
42
43
    /**
44
     * Is the object considered absolute
45
     *
46
     * @var int
47
     */
48
    protected $isAbsolute = self::IS_RELATIVE;
49
50
    /**
51
     * new instance
52
     *
53
     * @param null|string $str the component value
54
     */
55
    abstract public function __construct($str);
56
57
    /**
58
     * Returns whether or not the component is absolute or not
59
     *
60
     * @return bool
61
     */
62 27
    public function isAbsolute()
63
    {
64 27
        return $this->isAbsolute === self::IS_ABSOLUTE;
65
    }
66
67
    /**
68
     * Returns an instance with the specified string
69
     *
70
     * This method MUST retain the state of the current instance, and return
71
     * an instance that contains the modified data
72
     *
73
     * @param string $value
74
     *
75
     * @return static
76
     */
77 612
    public function withContent($value = null)
78
    {
79 612
        if ($value === $this->getContent()) {
80 228
            return $this;
81
        }
82
83 531
        return new static($value);
84
    }
85
86
    /**
87
     * DEPRECATION WARNING! This method will be removed in the next major point release
88
     *
89
     * @deprecated deprecated since version 4.2
90
     *
91
     * @see withContent
92
     *
93
     * Returns an instance with the specified string
94
     *
95
     * This method MUST retain the state of the current instance, and return
96
     * an instance that contains the modified data
97
     *
98
     * @param string $value
99
     *
100
     * @return static
101
     */
102
    public function modify($value)
103
    {
104
        return $this->withContent($value);
105
    }
106
107
    /**
108
     * Returns the component literal value
109
     *
110
     * @return string|null
111
     */
112
    abstract public function getContent();
113
114
    /**
115
     * Returns the instance string representation; If the
116
     * instance is not defined an empty string is returned
117
     *
118
     * @return string
119
     */
120 1117
    public function __toString()
121
    {
122 1117
        return (string) $this->getContent();
123
    }
124
125
    /**
126
     * Returns the instance string representation
127
     * with its optional URI delimiters
128
     *
129
     * @return string
130
     */
131 824
    public function getUriComponent()
132
    {
133 824
        return $this->__toString();
134
    }
135
136
    /**
137
     * Returns an instance with the modified segment
138
     *
139
     * This method MUST retain the state of the current instance, and return
140
     * an instance that contains the modified component with the replaced data
141
     *
142
     * @param int                          $offset    the label offset to remove and replace by
143
     *                                                the given component
144
     * @param HierarchicalComponent|string $component the component added
145
     *
146
     * @return static
147
     */
148 72
    public function replace($offset, $component)
149
    {
150 72
        if (!empty($this->data) && !$this->hasKey($offset)) {
151 15
            return $this;
152
        }
153
154 57
        $source = iterator_to_array($this);
155 57
        $dest   = iterator_to_array($this->validateComponent($component));
156 57
        if ('' === $dest[count($dest) - 1]) {
157 12
            array_pop($dest);
158 8
        }
159
160 57
        $data = array_merge(array_slice($source, 0, $offset), $dest, array_slice($source, $offset + 1));
161 57
        if ($data === $this->data) {
162 3
            return $this;
163
        }
164
165 54
        return $this->newCollectionInstance($data);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->newCollectionInstance($data); (League\Uri\Interfaces\Collection) is incompatible with the return type documented by League\Uri\Components\Ab...hicalComponent::replace of type League\Uri\Components\Ab...ctHierarchicalComponent.

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...
166
    }
167
168
    /**
169
     * Validate a component as a HierarchicalComponent object
170
     *
171
     * @param HierarchicalComponent|string $component
172
     *
173
     * @return static
174
     */
175 189
    protected function validateComponent($component)
176
    {
177 189
        if (!$component instanceof HierarchicalComponent) {
178 138
            return $this->withContent($component);
179
        }
180
181 75
        return $component;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $component; (League\Uri\Interfaces\HierarchicalComponent) is incompatible with the return type documented by League\Uri\Components\Ab...nent::validateComponent of type League\Uri\Components\Ab...ctHierarchicalComponent.

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...
182
    }
183
184
    /**
185
     * DEPRECATION WARNING! This method will be removed in the next major point release
186
     *
187
     * @deprecated deprecated since version 4.2
188
     *
189
     * return a new instance from an array or a traversable object
190
     *
191
     * @param \Traversable|string[] $data The segments list
192
     * @param int                   $type one of the constant IS_ABSOLUTE or IS_RELATIVE
193
     *
194
     * @throws InvalidArgumentException If $type is not a recognized constant
195
     *
196
     * @return static
197
     */
198
    public static function createFromArray($data, $type = self::IS_RELATIVE)
199
    {
200
        static $type_list = [self::IS_ABSOLUTE => 1, self::IS_RELATIVE => 1];
201
        if (!isset($type_list[$type])) {
202
            throw new InvalidArgumentException('Please verify the submitted constant');
203
        }
204
205
        return new static(static::formatComponentString($data, $type));
0 ignored issues
show
Deprecated Code introduced by
The method League\Uri\Components\Ab...formatComponentString() has been deprecated with message: deprecated since version 4.2 Return a formatted component string according to its type

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
206
    }
207
208
    /**
209
     * DEPRECATION WARNING! This method will be removed in the next major point release
210
     *
211
     * @deprecated deprecated since version 4.2
212
     *
213
     * Return a formatted component string according to its type
214
     *
215
     * @param \Traversable|string[] $data The segments list
216
     * @param int                   $type
217
     *
218
     * @throws InvalidArgumentException If $data is invalid
219
     *
220
     * @return string
221
     */
222
    protected static function formatComponentString($data, $type)
223
    {
224
        $path = implode(static::$separator, static::validateIterator($data));
225
        if (self::IS_ABSOLUTE == $type) {
226
            return static::$separator.$path;
227
        }
228
229
        return $path;
230
    }
231
}
232