Completed
Push — master ( bf8da1...e4b361 )
by Zoltán
02:31
created

StringObject   C

Complexity

Total Complexity 56

Size/Duplication

Total Lines 574
Duplicated Lines 6.27 %

Coupling/Cohesion

Components 1
Dependencies 3

Test Coverage

Coverage 100%

Importance

Changes 12
Bugs 0 Features 4
Metric Value
wmc 56
c 12
b 0
f 4
lcom 1
cbo 3
dl 36
loc 574
ccs 134
cts 134
cp 1
rs 6.5957

38 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 7 2
A removeWhitespace() 0 5 1
A append() 0 5 1
A prepend() 0 5 1
A chars() 0 5 1
A length() 0 5 1
A charAt() 0 7 2
A toLower() 0 5 1
A toUpper() 0 5 1
A startsWith() 0 3 1
A endsWith() 0 3 1
A contains() 0 3 1
A containsAll() 9 9 3
A containsAny() 9 9 3
A substring() 0 10 2
A first() 9 9 2
A last() 9 9 2
A map() 0 7 2
A split() 0 5 2
A segment() 0 6 2
A lastSegment() 0 6 1
A firstSegment() 0 6 1
A splitCamelCase() 0 6 1
A splitSnakeCase() 0 6 1
A caseFree() 0 5 1
A limit() 0 9 2
A toTitleCase() 0 5 1
A toSnakeCase() 0 5 1
A toPascalCase() 0 6 1
A toCamelCase() 0 21 3
A createClone() 0 10 2
A __toString() 0 3 1
A toString() 0 3 1
A count() 0 3 1
A getIterator() 0 3 1
A shouldReceiveType() 0 3 1
A getCurrentValue() 0 3 1
A processResult() 0 9 3

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like StringObject 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 StringObject, and based on these observations, apply Extract Interface, too.

1
<?php namespace BuildR\Utils;
2
3
use BuildR\Foundation\Exception\InvalidArgumentException;
4
use BuildR\Foundation\Object\StringConvertibleInterface;
5
use BuildR\Utils\Extension\AbstractExtensionReceiver;
6
use BuildR\Utils\Extension\StringObjectExtensionInterface;
7
use \Countable;
8
use \IteratorAggregate;
9
use \ArrayIterator;
10
11
/**
12
 * This object represents a string in OO format and allow nifty manipulation
13
 * and analysis of the provided string.
14
 *
15
 * BuildR PHP Framework
16
 *
17
 * @author Zoltán Borsos <[email protected]>
18
 * @package Utils
19
 *
20
 * @copyright    Copyright 2016, Zoltán Borsos.
21
 * @license      https://github.com/BuildrPHP/Utils/blob/master/LICENSE.md
22
 * @link         https://github.com/BuildrPHP/Utils
23
 */
24
class StringObject
25
    extends AbstractExtensionReceiver
0 ignored issues
show
Coding Style introduced by
The extends keyword must be on the same line as the class name
Loading history...
Coding Style introduced by
Expected 0 spaces between "AbstractExtensionReceiver" and comma; 1 found
Loading history...
26
    implements StringConvertibleInterface, Countable, IteratorAggregate {
0 ignored issues
show
Coding Style introduced by
The implements keyword must be on the same line as the class name
Loading history...
27
28
    /**
29
     * @type string
30
     */
31
    private $string;
32
33
    /**
34
     * StringObject constructor.
35
     *
36
     * @param $string
37
     *
38
     * @throws \BuildR\Foundation\Exception\InvalidArgumentException
39
     */
40 92
    public function __construct($string) {
41 92
        if(!is_string($string)) {
42 3
            throw new InvalidArgumentException('The given value is not a string!');
43
        }
44
45 89
        $this->string = $string;
46 89
    }
47
48
    /**
49
     * Remove all whitespace character from the given string
50
     *
51
     * @return \BuildR\Utils\StringObject
52
     */
53 4
    public function removeWhitespace() {
54 4
        $result = str_replace(["\\r", "\\n", "\\t", "\\0"], '', $this->string);
55
56 4
        return $this->createClone($result);
57
    }
58
59
    /**
60
     * Appends the given substring to the current string
61
     *
62
     * @param string $substring
63
     *
64
     * @return \BuildR\Utils\StringObject
65
     */
66 5
    public function append($substring) {
67 5
        $newValue = $this->string . $substring;
68
69 5
        return $this->createClone($newValue);
70
    }
71
72
    /**
73
     * Prepends the given substring to the current string
74
     *
75
     * @param string $substring
76
     *
77
     * @return \BuildR\Utils\StringObject
78
     */
79 6
    public function prepend($substring) {
80 6
        $newValue = $substring . $this->string;
81
82 6
        return $this->createClone($newValue);
83
    }
84
85
    /**
86
     * Return an array where each element represent a single character from the current string
87
     *
88
     * @return array
89
     */
90 9
    public function chars() {
91 9
        $string = $this->string;
92
93 9
        return preg_split('/(?<!^)(?!$)/u', $string);
94
    }
95
96
    /**
97
     * Determines the length of the current string
98
     *
99
     * @return int
100
     */
101 3
    public function length() {
102 3
        $string = $this->string;
103
104 3
        return mb_strlen($string);
105
    }
106
107
    /**
108
     * Determines that character exist in the given position
109
     * If the character is a whitespace this function will return
110
     * TRUE to indicate that index is part of the string
111
     *
112
     * This function counts characters from 1
113
     *
114
     * @param int $index
115
     *
116
     * @return \BuildR\Utils\StringObject
117
     */
118 5
    public function charAt($index) {
119 5
        $index = $index - 1;
120 5
        $chars = $this->chars();
121 5
        $char = (isset($chars[$index])) ? $chars[$index] : '';
122
123 5
        return $this->createClone($char);
124
    }
125
126
    /**
127
     * Format the string to lowercase
128
     *
129
     * @return \BuildR\Utils\StringObject
130
     */
131 6
    public function toLower() {
132 6
        $newValue = mb_strtolower($this->string);
133
134 6
        return $this->createClone($newValue);
135
    }
136
137
    /**
138
     * Format the string to uppercase
139
     *
140
     * @return \BuildR\Utils\StringObject
141
     */
142 4
    public function toUpper() {
143 4
        $newValue = mb_strtoupper($this->string);
144
145 4
        return $this->createClone($newValue);
146
    }
147
148
    /**
149
     * Determines that the current string starts with
150
     * the given substring
151
     *
152
     * @param string $match
153
     *
154
     * @return bool
155
     *
156
     * @codeCoverageIgnore
157
     */
158
    public function startsWith($match) {
159
        return StringUtils::startsWith($this->string, $match);
160
    }
161
162
    /**
163
     * Determines that the current string ends with
164
     * the given substring
165
     *
166
     * @param string $match
167
     *
168
     * @return bool
169
     *
170
     * @codeCoverageIgnore
171
     */
172
    public function endsWith($match) {
173
        return StringUtils::endsWith($this->string, $match);
174
    }
175
176
    /**
177
     * Determines that the given string contains the
178
     * given substring
179
     *
180
     * @param string $match
181
     *
182
     * @return bool
183
     *
184
     * @codeCoverageIgnore
185
     */
186
    public function contains($match) {
187
        return StringUtils::contains($this->string, $match);
188
    }
189
190
    /**
191
     * Determines that the current string contains ALL of the
192
     * given substring(s)
193
     *
194
     * @param array $matches
195
     *
196
     * @return bool
197
     */
198 4 View Code Duplication
    public function containsAll(array $matches = []) {
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...
199 4
        foreach($matches as $match) {
200 4
            if(!StringUtils::contains($this->string, $match)) {
201 1
                return FALSE;
202
            }
203 4
        }
204
205 3
        return TRUE;
206
    }
207
208
    /**
209
     * Determines that the current string contains ANY of the
210
     * given substring(s)
211
     *
212
     * @param array $matches
213
     *
214
     * @return bool
215
     */
216 3 View Code Duplication
    public function containsAny(array $matches = []) {
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...
217 3
        foreach($matches as $match) {
218 3
            if(StringUtils::contains($this->string, $match)) {
219 2
                return TRUE;
220
            }
221 1
        }
222
223 1
        return FALSE;
224
    }
225
226
    /**
227
     * Returns the defined part of the current string
228
     *
229
     * @param int $start Tha starting character (If -1 starts from the end)
230
     * @param int|NULL $length
231
     *
232
     * @return \BuildR\Utils\StringObject
233
     */
234 20
    public function substring($start, $length = NULL) {
235
        //TODO: Fix to PHP #42101
236 20
        if($length === NULL) {
237 8
            $newValue = mb_substr($this->string, $start);
238 8
        } else {
239 12
            $newValue = mb_substr($this->string, $start, $length);
240
        }
241
242 20
        return $this->createClone($newValue);
243
    }
244
245
    /**
246
     * Returns the firs X character from the string. If the provided number higher
247
     * tha the string length the full string will be returned but not more.
248
     *
249
     * @param int $charCount
250
     *
251
     * @return \BuildR\Utils\StringObject
252
     */
253 9 View Code Duplication
    public function first($charCount) {
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...
254 9
        if($charCount <= 0) {
255 2
            return $this->createClone('');
256
        }
257
258 7
        $newValue = $this->substring(0, (int) $charCount);
259
260 7
        return $this->createClone($newValue);
261
    }
262
263
    /**
264
     * Returns the last X character from the current string
265
     *
266
     * @param int $charCount
267
     *
268
     * @return \BuildR\Utils\StringObject
269
     */
270 7 View Code Duplication
    public function last($charCount) {
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...
271 7
        if($charCount <= 0) {
272 2
            return $this->createClone('');
273
        }
274
275 5
        $newValue = $this->substring(($charCount * -1));
276
277 5
        return $this->createClone($newValue);
278
    }
279
280
    /**
281
     * Iterates over parts of the string. The current string split through
282
     * the given delimiter and tha closure will be called on all elements.
283
     *
284
     * The closer takes two arguments:
285
     *  - (string) $part The current part of the string
286
     *  - (string) $delimiter the delimiter passed to this function
287
     *
288
     * @param $delimiter
289
     * @param callable $callback
290
     */
291 3
    public function map($delimiter, callable $callback) {
292 3
        $parts = $this->split($delimiter);
293
294 3
        foreach($parts as $part) {
295 3
            call_user_func_array($callback, [$part, $delimiter]);
296 3
        }
297 3
    }
298
299
    /**
300
     * Split string along the delimiter
301
     *
302
     * @param string $delimiter
303
     *
304
     * @return array
305
     */
306 15
    public function split($delimiter) {
307 15
        $delimiter = (empty($delimiter)) ? ' ' : $delimiter;
308
309 15
        return explode($delimiter, $this->string);
310
    }
311
312
    /**
313
     * Split the string along the delimiter and returns
314
     * the given index from the segments
315
     *
316
     * @param string $delimiter
317
     * @param int $index
318
     *
319
     * @return \BuildR\Utils\StringObject
320
     */
321 5
    public function segment($delimiter, $index) {
322 5
        $parts = $this->split($delimiter);
323 5
        $returnValue = (isset($parts[$index - 1])) ? $parts[$index - 1] : '';
324
325 5
        return $this->createClone($returnValue);
326
    }
327
328
    /**
329
     * Split string along the delimiter and
330
     * return the last segment
331
     *
332
     * @param string $delimiter
333
     *
334
     * @return \BuildR\Utils\StringObject
335
     *
336
     * @codeCoverageIgnore
337
     */
338
    public function lastSegment($delimiter) {
339
        $parts = $this->split($delimiter);
340
        $returnValue = end($parts);
341
342
        return $this->createClone($returnValue);
343
    }
344
345
    /**
346
     * Split the string along the delimiter and
347
     * returns the first segment
348
     *
349
     * @param string $delimiter
350
     *
351
     * @return \BuildR\Utils\StringObject
352
     *
353
     * @codeCoverageIgnore
354
     */
355
    public function firstSegment($delimiter) {
356
        $parts = $this->split($delimiter);
357
        $returnValue = current($parts);
358
359
        return $this->createClone($returnValue);
360
    }
361
362
    /**
363
     * Split down cameCase or PascalCase strings
364
     *
365
     * Example:
366
     * helloWorld -> hello World
367
     * ExampleHelloWorld -> ExampleHelloWorld
368
     *
369
     * @return \BuildR\Utils\StringObject
370
     */
371 5
    public function splitCamelCase() {
372 5
        preg_match_all('/((?:^|[\p{Lu}])[\p{Ll}_]+)/mu', $this->string, $matches);
373 5
        $returnValue = implode(' ', $matches[0]);
374
375 5
        return $this->createClone($returnValue);
376
    }
377
378
    /**
379
     * Split snake_case string.
380
     *
381
     * Example
382
     * hello_world -> hello world
383
     *
384
     * @return \BuildR\Utils\StringObject
385
     */
386 5
    public function splitSnakeCase() {
387 5
        $parts = $this->split('_');
388 5
        $returnValue = implode(' ', $parts);
389
390 5
        return $this->createClone($returnValue);
391
    }
392
393
    /**
394
     * Try to split any case method into words.
395
     * This convert the following cases into words:
396
     *
397
     *  - PascalCase
398
     *  - camelCase
399
     *  - snake_case
400
     *
401
     * Example:
402
     * helloExampleWorld_olah -> hello Example World olah
403
     *
404
     * @return \BuildR\Utils\StringObject
405
     */
406 2
    public function caseFree() {
407 2
        $returnValue = $this->splitSnakeCase()->splitCamelCase();
408
409 2
        return $this->createClone((string) $returnValue);
410
    }
411
412
    /**
413
     * Limit the length of the current string. If the current string is
414
     * longer tha the given limit chopped down to limit and the end
415
     * substring will be concatenated.
416
     *
417
     * @param int $limit
418
     * @param string $end
419
     *
420
     * @return \BuildR\Utils\StringObject
421
     */
422 3
    public function limit($limit = 120, $end = '...') {
423 3
        $returnValue = $this->string;
424
425 3
        if($this->length() > $limit) {
426 2
            $returnValue = rtrim((string) $this->first($limit)->toString()) . $end;
427 2
        }
428
429 3
        return $this->createClone($returnValue);
430
    }
431
432
    /**
433
     * Convert current string to 'Title Case'
434
     *
435
     * Example:
436
     * 'hello world example' -> 'Hello World Example'
437
     *
438
     * @return \BuildR\Utils\StringObject
439
     */
440 2
    public function toTitleCase() {
441 2
        $returnValue = mb_convert_case((string) $this->toLower(), MB_CASE_TITLE);
442
443 2
        return $this->createClone($returnValue);
444
    }
445
446
    /**
447
     * Convert current string to snake_case
448
     *
449
     * @return \BuildR\Utils\StringObject
450
     */
451 2
    public function toSnakeCase() {
452 2
        $newValue = implode('_', $this->split(' '));
453
454 2
        return $this->createClone($newValue);
455
    }
456
457
    /**
458
     * Convert current string to PascalCase
459
     *
460
     * @return \BuildR\Utils\StringObject
461
     */
462 2
    public function toPascalCase() {
463 2
        $newValue = $this->toLower()->toTitleCase()->toString();
464 2
        $newValue = implode('', explode(' ', $newValue));
465
466 2
        return $this->createClone($newValue);
467
    }
468
469
    /**
470
     * Convert current string to camelCase
471
     *
472
     * @return \BuildR\Utils\StringObject
473
     */
474 2
    public function toCamelCase() {
475 2
        $parts = $this->toLower()->split(' ');
476 2
        $index = 0;
477 2
        $result = '';
478
479
        //Yeah, Its ugly. But fast...
480 2
        foreach($parts as $part) {
481 2
            if($index === 0) {
482 2
                $index++;
483 2
                $result .= $part;
484
485 2
                continue;
486
            }
487
488
            //TODO: implement own ucfirst and ucword method in this class for MB support
489 2
            $result .= $this->createClone($part)->toTitleCase()->toString();
490 2
            $index++;
491 2
        }
492
493 2
        return $this->createClone($result);
494
    }
495
496
    /**
497
     * Clone the current object a set a new value (base string)
498
     * on the clone.
499
     *
500
     * This method allow instances immutability, but allow instances
501
     * to retain loaded extensions.
502
     *
503
     * @param $newValue
504
     *
505
     * @return \BuildR\Utils\StringObject
506
     */
507 69
    protected function createClone($newValue) {
508 69
        if($newValue instanceof StringObject) {
509 12
            $newValue == $newValue->toString();
510 12
        }
511
512 69
        $newInstance = clone $this;
513 69
        $newInstance->string = $newValue;
514
515 69
        return $newInstance;
516
    }
517
518
    //===========================================
519
    // StringConvertibleInterface Implementation
520
    //===========================================
521
522
    /**
523
     * {@inheritDoc}
524
     */
525 16
    public function __toString() {
526 16
        return $this->string;
527
    }
528
529
    /**
530
     * {@inheritDoc}
531
     */
532 74
    public function toString() {
533 74
        return $this->string;
534
    }
535
536
    // =========================
537
    // Countable Implementation
538
    // =========================
539
540
    /**
541
     * {@inheritDoc}
542
     *
543
     * @codeCoverageIgnore
544
     */
545
    public function count() {
546
        return $this->length();
547
    }
548
549
    // =============================
550
    // ArrayIterator Implementation
551
    // =============================
552
553
    /**
554
     * {@inheritDoc}
555
     */
556 4
    public function getIterator() {
557 4
        return new ArrayIterator($this->chars());
558
    }
559
560
    // ==========================================
561
    // ExtensionReceiverInterface Implementation
562
    // ==========================================
563
564
    /**
565
     * {@inheritDoc}
566
     *
567
     * @internal
568
     */
569 5
    public function shouldReceiveType() {
570 5
        return StringObjectExtensionInterface::class;
571
    }
572
573
    /**
574
     * {@inheritDoc}
575
     *
576
     * @internal
577
     */
578 2
    public function getCurrentValue() {
579 2
        return $this->string;
580
    }
581
582
    /**
583
     * {@inheritDoc}
584
     *
585
     * @internal
586
     */
587 2
    public function processResult(array $result) {
588 2
        list($result, $isRaw) = array_values($result);
589
590 2
        if(!$isRaw && is_string($result)) {
591 1
            return $this->createClone($result);
592
        }
593
594 1
        return $result;
595
    }
596
597
}
598