StringType   C
last analyzed

Complexity

Total Complexity 56

Size/Duplication

Total Lines 325
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 13

Test Coverage

Coverage 99.04%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 56
c 1
b 0
f 0
lcom 1
cbo 13
dl 0
loc 325
ccs 103
cts 104
cp 0.9904
rs 6.5957

20 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 2
D __invoke() 0 29 9
A get() 0 4 1
A create() 0 4 1
A explode() 0 7 3
A pluralize() 0 4 1
A singularize() 0 4 1
A strpos() 0 8 2
A strrpos() 0 8 2
A isSemVer() 0 11 4
A __toString() 0 4 1
A toBoolType() 0 4 1
A toBoolean() 0 4 1
A toDateTime() 0 4 1
A valueOf() 0 4 1
A subStrUntil() 0 6 1
A subStrAfter() 0 4 1
C subStrBetween() 0 25 8
A getInflector() 0 4 1
C asString() 0 35 14

How to fix   Complexity   

Complex Class

Complex classes like StringType often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use StringType, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Tdn\PhpTypes\Type;
6
7
use Doctrine\Common\Inflector\Inflector;
8
use Doctrine\Common\Collections\Collection as CollectionInterface;
9
use Stringy\Stringy;
10
use Tdn\PhpTypes\Exception\InvalidTypeCastException;
11
use Tdn\PhpTypes\Type\Traits\Boxable;
12
use Tdn\PhpTypes\Type\Traits\Transmutable;
13
use Tdn\PhpTypes\Exception\InvalidTransformationException;
14
15
/**
16
 * Class StringType.
17
 *
18
 * A StringType is a TypeInterface implementation that wraps around a regular PHP string.
19
 * This object extends Stringy.
20
 *
21
 * {@inheritdoc}
22
 */
23
class StringType extends Stringy implements TransmutableTypeInterface, ValueTypeInterface
24
{
25
    use Transmutable;
26
    use Boxable;
27
28
    /**
29
     * StringType constructor.
30
     * Explicitly removing parent constructor. Type conversion should be done through StringType::from($mixed).
31
     *
32
     * @param string      $str
33
     * @param string|null $encoding
34
     */
35 91
    public function __construct(string $str, string $encoding = null)
36
    {
37 91
        $this->str = $str;
38 91
        $this->encoding = $encoding ?: \mb_internal_encoding();
39
        //Explicitly not calling parent construct.
40 91
    }
41
42
    /**
43
     * {@inheritdoc}
44
     *
45
     * @return string|int|float|bool|array
46
     */
47 20
    public function __invoke(int $toType = Type::STRING)
48
    {
49
        switch ($toType) {
50 20
            case Type::STRING:
51 11
                return $this->str;
52 10
            case Type::INT:
53 2
                if (is_numeric((string) $this)) {
54 1
                    return IntType::valueOf($this)->get();
55
                }
56
57 1
                break;
58 9
            case Type::FLOAT:
59 2
                if (is_numeric((string) $this)) {
60 1
                    return FloatType::valueOf($this)->get();
61
                }
62
63 1
                break;
64 8
            case Type::ARRAY:
65 2
                if ($this->contains(',')) {
66 1
                    return $this->explode(',')->toArray();
67
                }
68
69 1
                break;
70 7
            case Type::BOOL:
71 1
                return BooleanType::valueOf($this)->get();
72
        }
73
74 9
        throw new InvalidTypeCastException(static::class, $this->getTranslatedType($toType));
75
    }
76
77
    /**
78
     * @return string
79
     */
80 35
    public function get()
81
    {
82 35
        return (string) $this;
83
    }
84
85
    /**
86
     * Creates a new static instance from string.
87
     *
88
     * Mainly here for code completion purposes...
89
     *
90
     * @param string $str
91
     * @param string $encoding
92
     *
93
     * @return StringType
94
     */
95 51
    public static function create($str = '', $encoding = 'UTF-8'): StringType
96
    {
97 51
        return new static($str, $encoding);
98
    }
99
100
    /**
101
     * Explodes current instance into a collection object.
102
     *
103
     * @param string $delimiter
104
     * @param int    $limit     default PHP_INT_MAX
105
     * @param bool   $trim      default false, greedely trim the string before exploding
106
     *
107
     * @return Collection|StringType[]
108
     */
109 8
    public function explode(string $delimiter, int $limit = PHP_INT_MAX, bool $trim = true): Collection
110
    {
111 8
        $str = ($trim) ? $this->regexReplace('[[:space:]]', '')->str : $this->str;
112 8
        $delimiter = ($trim) ? static::create($delimiter)->regexReplace('[[:space:]]', '')->str : $delimiter;
113
114 8
        return new Collection(explode($delimiter, $str, $limit), static::class);
115
    }
116
117
    /**
118
     * Pluralizes the string.
119
     *
120
     * @return StringType
121
     */
122 1
    public function pluralize(): StringType
123
    {
124 1
        return static::create($this->getInflector()->pluralize((string) $this->str), $this->encoding);
125
    }
126
127
    /**
128
     * Singularizes the string.
129
     *
130
     * @return StringType
131
     */
132 1
    public function singularize(): StringType
133
    {
134 1
        return static::create($this->getInflector()->singularize((string) $this->str), $this->encoding);
135
    }
136
137
    /**
138
     * Returns position of the first occurrence of subStr null if not present.
139
     *
140
     * @param string $subStr        Substring
141
     * @param int    $offset        Chars to offset from start
142
     * @param bool   $caseSensitive Enable case sensitivity
143
     *
144
     * @return IntType
145
     */
146 3
    public function strpos(string $subStr, int $offset = 0, bool $caseSensitive = false): IntType
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
147
    {
148 3
        $res = ($caseSensitive) ?
149 1
            mb_strpos($this->str, $subStr, $offset, $this->encoding) :
150 3
            mb_stripos($this->str, $subStr, $offset, $this->encoding);
151
152 3
        return new IntType($res);
153
    }
154
155
    /**
156
     * Returns position of the last occurrence of subStr null if not present.
157
     *
158
     * @param string $subStr        Substring
159
     * @param int    $offset        Chars to offset from start
160
     * @param bool   $caseSensitive Enable case sensitivity
161
     *
162
     * @return IntType
163
     */
164 1
    public function strrpos(string $subStr, int $offset = 0, bool $caseSensitive = false): IntType
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
165
    {
166 1
        $res = ($caseSensitive) ?
167 1
            mb_strrpos($this->str, $subStr, $offset, $this->encoding) :
168 1
            mb_strripos($this->str, $subStr, $offset, $this->encoding);
169
170 1
        return new IntType($res);
171
    }
172
173
    /**
174
     * @return BooleanType
175
     */
176 6
    public function isSemVer(): BooleanType
177
    {
178 6
        $parts = $this->explode('.');
179 6
        foreach ($parts as $possibleNumber) {
180 6
            if (false === is_numeric($possibleNumber->toString())) {
181 6
                return new BooleanType(false);
182
            }
183
        }
184
185 6
        return ($this->countSubstr('.') > 1) ? new BooleanType(true) : new BooleanType(false);
186
    }
187
188
    /**
189
     * @return string The current value of the $str property
190
     */
191 43
    public function __toString(): string
192
    {
193 43
        return (string) $this->str;
194
    }
195
196
    /**
197
     * Override trait to remove spaces.
198
     *
199
     * @return BooleanType
200
     */
201 1
    public function toBoolType(): BooleanType
202
    {
203 1
        return BooleanType::valueOf($this->regexReplace('[[:space:]]', '')->str);
204
    }
205
206
    /**
207
     * @return BooleanType
208
     */
209 1
    public function toBoolean(): BooleanType
210
    {
211 1
        return $this->toBoolType();
212
    }
213
214
    /**
215
     * @return DateTimeType
216
     */
217 1
    public function toDateTime(): DateTimeType
218
    {
219 1
        return DateTimeType::valueOf($this);
220
    }
221
222
    /**
223
     * {@inheritdoc}
224
     *
225
     * @return StringType
226
     */
227 54
    public static function valueOf($mixed): StringType
228
    {
229 54
        return new static(self::asString($mixed));
230
    }
231
232
    /**
233
     * Returns substring from beginning until first instance of subsStr.
234
     *
235
     * @param string $subStr
236
     * @param bool $includingSubStr
237
     * @param bool $caseSensitive
238
     *
239
     * @return static
240
     */
241 1
    public function subStrUntil($subStr, $includingSubStr = false, $caseSensitive = false)
242
    {
243 1
        $fromSubStr = $this->str[0];
244
245 1
        return $this->subStrBetween($fromSubStr, $subStr, false, !$includingSubStr, $caseSensitive);
246
    }
247
248
    /**
249
     * Returns substring from first instance of subStr to end of string.
250
     * @param $subStr
251
     * @param bool $includingSubStr
252
     * @param bool $caseSensitive
253
     *
254
     * @return static
255
     */
256 1
    public function subStrAfter($subStr, $includingSubStr = false, $caseSensitive = false)
257
    {
258 1
        return $this->subStrBetween($subStr, null, !$includingSubStr, false, $caseSensitive);
259
    }
260
261
    /**
262
     * Returns substring between fromSubStr to toSubStr. End of string if toSubStr is not set.
263
     *
264
     * @param string $fromSubStr
265
     * @param string $toSubStr
266
     * @param bool $excludeFromSubStr
267
     * @param bool $excludeToSubStr
268
     * @param bool $caseSensitive
269
     * @return self
270
     */
271 2
    private function subStrBetween(
272
        $fromSubStr,
273
        $toSubStr = '',
274
        $excludeFromSubStr = false,
275
        $excludeToSubStr = false,
276
        $caseSensitive = false
277
    ) {
278 2
        $fromIndex = 0;
279 2
        $toIndex = mb_strlen($this->str);
280 2
        $str = self::create($this->str);
281 2
        if ($str->contains($fromSubStr)) {
282 2
            $fromIndex = $this->strpos($fromSubStr, 0, $caseSensitive)->get();
283 2
            $fromIndex = ($excludeFromSubStr) ? $fromIndex + mb_strlen($fromSubStr, $this->encoding) : $fromIndex;
284 2
            if ($fromIndex < 0) {
285
                throw new \LogicException('To cannot be before from.');
286
            }
287 2
            if (!empty($toSubStr) && $str->contains($toSubStr)) {
288 1
                $toIndex = $this->strpos($toSubStr, $fromIndex, $caseSensitive)->get();
289 1
                $toIndex = ($excludeToSubStr) ?
290 1
                    $toIndex - $fromIndex :  ($toIndex - $fromIndex) + mb_strlen($toSubStr, $this->encoding);
291
            }
292
        }
293
294 2
        return ($toSubStr) ? $str->substr($fromIndex, $toIndex) : $str->substr($fromIndex);
295
    }
296
297
    /**
298
     * @return Inflector
299
     */
300 2
    private function getInflector(): Inflector
301
    {
302 2
        return new Inflector();
303
    }
304
305
    /**
306
     * Returns a mixed variable as a string.
307
     *
308
     * @param mixed $mixed
309
     *
310
     * @return string
311
     */
312 54
    private static function asString($mixed): string
313
    {
314 54
        if ($mixed instanceof ValueTypeInterface) {
315 35
            $mixed = $mixed->get();
316
        }
317
318 54
        if ($mixed instanceof CollectionInterface) {
319
            //Handle as array.
320 4
            $mixed = $mixed->toArray();
321
        }
322
323 54
        $type = strtolower(gettype($mixed));
324
        switch ($type) {
325 54
            case 'string':
326 51
            case 'integer':
327 36
            case 'float':
328 36
            case 'double':
329 49
                return (string) $mixed;
330 6
            case 'boolean':
331 1
                return ($mixed) ? 'true' : 'false';
332 6
            case 'array':
333 4
                return implode(', ', $mixed);
334 3
            case 'object':
335 2
                if (method_exists($mixed, '__toString')) {
336 1
                    return (string) $mixed;
337
                }
338
339 1
                throw new InvalidTransformationException($type, static::class);
340 2
            case 'resource':
341 1
                return get_resource_type($mixed);
342 1
            case 'null':
343
            default:
344 1
                throw new InvalidTransformationException($type, static::class);
345
        }
346
    }
347
}
348