Completed
Push — master ( e4b361...00fe12 )
by Zoltán
02:44
created

StringObject::ucfirst()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 5
ccs 0
cts 0
cp 0
rs 9.4285
cc 1
eloc 3
nc 1
nop 0
crap 2
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
     * Format the current string first character to uppercase.
150
     * Leave any other character untouched
151
     *
152
     * @return \BuildR\Utils\StringObject
153
     *
154
     * @codeCoverageIgnore
155
     */
156
    public function ucfirst() {
157
        $result = StringUtils::multiByteUcfirst($this->string);
158
159
        return $this->createClone($result);
160
    }
161
162
    /**
163
     * Split the current string trough spaces and makes every words first
164
     * character uppercase. Leave any other character untouched
165
     *
166
     * @return \BuildR\Utils\StringObject
167
     *
168
     * @codeCoverageIgnore
169
     */
170
    public function ucwords() {
171
        $result = StringUtils::multiByteUcwords($this->string);
172
173
        return $this->createClone($result);
174
    }
175
176
    /**
177
     * Determines that the current string starts with
178
     * the given substring
179
     *
180
     * @param string $match
181
     *
182
     * @return bool
183
     *
184
     * @codeCoverageIgnore
185
     */
186
    public function startsWith($match) {
187
        return StringUtils::startsWith($this->string, $match);
188
    }
189
190
    /**
191
     * Determines that the current string ends with
192
     * the given substring
193
     *
194
     * @param string $match
195
     *
196
     * @return bool
197
     *
198
     * @codeCoverageIgnore
199
     */
200
    public function endsWith($match) {
201
        return StringUtils::endsWith($this->string, $match);
202
    }
203
204
    /**
205
     * Determines that the given string contains the
206
     * given substring
207
     *
208
     * @param string $match
209
     *
210
     * @return bool
211
     *
212
     * @codeCoverageIgnore
213
     */
214
    public function contains($match) {
215
        return StringUtils::contains($this->string, $match);
216
    }
217
218
    /**
219
     * Determines that the current string contains ALL of the
220
     * given substring(s)
221
     *
222
     * @param array $matches
223
     *
224
     * @return bool
225
     */
226 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...
227 4
        foreach($matches as $match) {
228 4
            if(!StringUtils::contains($this->string, $match)) {
229 1
                return FALSE;
230
            }
231 4
        }
232
233 3
        return TRUE;
234
    }
235
236
    /**
237
     * Determines that the current string contains ANY of the
238
     * given substring(s)
239
     *
240
     * @param array $matches
241
     *
242
     * @return bool
243
     */
244 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...
245 3
        foreach($matches as $match) {
246 3
            if(StringUtils::contains($this->string, $match)) {
247 2
                return TRUE;
248
            }
249 1
        }
250
251 1
        return FALSE;
252
    }
253
254
    /**
255
     * Returns the defined part of the current string
256
     *
257
     * @param int $start Tha starting character (If -1 starts from the end)
258
     * @param int|NULL $length
259
     *
260
     * @return \BuildR\Utils\StringObject
261
     */
262 20
    public function substring($start, $length = NULL) {
263
        //TODO: Fix to PHP #42101
264 20
        if($length === NULL) {
265 8
            $newValue = mb_substr($this->string, $start);
266 8
        } else {
267 12
            $newValue = mb_substr($this->string, $start, $length);
268
        }
269
270 20
        return $this->createClone($newValue);
271
    }
272
273
    /**
274
     * Returns the firs X character from the string. If the provided number higher
275
     * tha the string length the full string will be returned but not more.
276
     *
277
     * @param int $charCount
278
     *
279
     * @return \BuildR\Utils\StringObject
280
     */
281 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...
282 9
        if($charCount <= 0) {
283 2
            return $this->createClone('');
284
        }
285
286 7
        $newValue = $this->substring(0, (int) $charCount);
287
288 7
        return $this->createClone($newValue);
289
    }
290
291
    /**
292
     * Returns the last X character from the current string
293
     *
294
     * @param int $charCount
295
     *
296
     * @return \BuildR\Utils\StringObject
297
     */
298 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...
299 7
        if($charCount <= 0) {
300 2
            return $this->createClone('');
301
        }
302
303 5
        $newValue = $this->substring(($charCount * -1));
304
305 5
        return $this->createClone($newValue);
306
    }
307
308
    /**
309
     * Iterates over parts of the string. The current string split through
310
     * the given delimiter and tha closure will be called on all elements.
311
     *
312
     * The closer takes two arguments:
313
     *  - (string) $part The current part of the string
314
     *  - (string) $delimiter the delimiter passed to this function
315
     *
316
     * @param $delimiter
317
     * @param callable $callback
318
     */
319 3
    public function map($delimiter, callable $callback) {
320 3
        $parts = $this->split($delimiter);
321
322 3
        foreach($parts as $part) {
323 3
            call_user_func_array($callback, [$part, $delimiter]);
324 3
        }
325 3
    }
326
327
    /**
328
     * Split string along the delimiter
329
     *
330
     * @param string $delimiter
331
     *
332
     * @return array
333
     */
334 15
    public function split($delimiter) {
335 15
        $delimiter = (empty($delimiter)) ? ' ' : $delimiter;
336
337 15
        return explode($delimiter, $this->string);
338
    }
339
340
    /**
341
     * Split the string along the delimiter and returns
342
     * the given index from the segments
343
     *
344
     * @param string $delimiter
345
     * @param int $index
346
     *
347
     * @return \BuildR\Utils\StringObject
348
     */
349 5
    public function segment($delimiter, $index) {
350 5
        $parts = $this->split($delimiter);
351 5
        $returnValue = (isset($parts[$index - 1])) ? $parts[$index - 1] : '';
352
353 5
        return $this->createClone($returnValue);
354
    }
355
356
    /**
357
     * Split string along the delimiter and
358
     * return the last segment
359
     *
360
     * @param string $delimiter
361
     *
362
     * @return \BuildR\Utils\StringObject
363
     *
364
     * @codeCoverageIgnore
365
     */
366
    public function lastSegment($delimiter) {
367
        $parts = $this->split($delimiter);
368
        $returnValue = end($parts);
369
370
        return $this->createClone($returnValue);
371
    }
372
373
    /**
374
     * Split the string along the delimiter and
375
     * returns the first segment
376
     *
377
     * @param string $delimiter
378
     *
379
     * @return \BuildR\Utils\StringObject
380
     *
381
     * @codeCoverageIgnore
382
     */
383
    public function firstSegment($delimiter) {
384
        $parts = $this->split($delimiter);
385
        $returnValue = current($parts);
386
387
        return $this->createClone($returnValue);
388
    }
389
390
    /**
391
     * Split down cameCase or PascalCase strings
392
     *
393
     * Example:
394
     * helloWorld -> hello World
395
     * ExampleHelloWorld -> ExampleHelloWorld
396
     *
397
     * @return \BuildR\Utils\StringObject
398
     */
399 5
    public function splitCamelCase() {
400 5
        preg_match_all('/((?:^|[\p{Lu}])[\p{Ll}_]+)/mu', $this->string, $matches);
401 5
        $returnValue = implode(' ', $matches[0]);
402
403 5
        return $this->createClone($returnValue);
404
    }
405
406
    /**
407
     * Split snake_case string.
408
     *
409
     * Example
410
     * hello_world -> hello world
411
     *
412
     * @return \BuildR\Utils\StringObject
413
     */
414 5
    public function splitSnakeCase() {
415 5
        $parts = $this->split('_');
416 5
        $returnValue = implode(' ', $parts);
417
418 5
        return $this->createClone($returnValue);
419
    }
420
421
    /**
422
     * Try to split any case method into words.
423
     * This convert the following cases into words:
424
     *
425
     *  - PascalCase
426
     *  - camelCase
427
     *  - snake_case
428
     *
429
     * Example:
430
     * helloExampleWorld_olah -> hello Example World olah
431
     *
432
     * @return \BuildR\Utils\StringObject
433
     */
434 2
    public function caseFree() {
435 2
        $returnValue = $this->splitSnakeCase()->splitCamelCase();
436
437 2
        return $this->createClone((string) $returnValue);
438
    }
439
440
    /**
441
     * Limit the length of the current string. If the current string is
442
     * longer tha the given limit chopped down to limit and the end
443
     * substring will be concatenated.
444
     *
445
     * @param int $limit
446
     * @param string $end
447
     *
448
     * @return \BuildR\Utils\StringObject
449
     */
450 3
    public function limit($limit = 120, $end = '...') {
451 3
        $returnValue = $this->string;
452
453 3
        if($this->length() > $limit) {
454 2
            $returnValue = rtrim((string) $this->first($limit)->toString()) . $end;
455 2
        }
456
457 3
        return $this->createClone($returnValue);
458
    }
459
460
    /**
461
     * Convert current string to 'Title Case'
462
     *
463
     * Example:
464
     * 'hello world example' -> 'Hello World Example'
465
     *
466
     * @return \BuildR\Utils\StringObject
467
     */
468 2
    public function toTitleCase() {
469 2
        $returnValue = mb_convert_case((string) $this->toLower(), MB_CASE_TITLE);
470
471 2
        return $this->createClone($returnValue);
472
    }
473
474
    /**
475
     * Convert current string to snake_case
476
     *
477
     * @return \BuildR\Utils\StringObject
478
     */
479 2
    public function toSnakeCase() {
480 2
        $newValue = implode('_', $this->split(' '));
481
482 2
        return $this->createClone($newValue);
483
    }
484
485
    /**
486
     * Convert current string to PascalCase
487
     *
488
     * @return \BuildR\Utils\StringObject
489
     */
490 2
    public function toPascalCase() {
491 2
        $newValue = $this->toLower()->toTitleCase()->toString();
492 2
        $newValue = implode('', explode(' ', $newValue));
493
494 2
        return $this->createClone($newValue);
495
    }
496
497
    /**
498
     * Convert current string to camelCase
499
     *
500
     * @return \BuildR\Utils\StringObject
501
     */
502 2
    public function toCamelCase() {
503 2
        $parts = $this->toLower()->split(' ');
504 2
        $index = 0;
505 2
        $result = '';
506
507
        //Yeah, Its ugly. But fast...
508 2
        foreach($parts as $part) {
509 2
            if($index === 0) {
510 2
                $index++;
511 2
                $result .= $part;
512
513 2
                continue;
514
            }
515
516 2
            $result .= (string) $this->createClone($part)->ucfirst();
517 2
            $index++;
518 2
        }
519
520 2
        return $this->createClone($result);
521
    }
522
523
    /**
524
     * Clone the current object a set a new value (base string)
525
     * on the clone.
526
     *
527
     * This method allow instances immutability, but allow instances
528
     * to retain loaded extensions.
529
     *
530
     * @param $newValue
531
     *
532
     * @return \BuildR\Utils\StringObject
533
     */
534 69
    protected function createClone($newValue) {
535 69
        if($newValue instanceof StringObject) {
536 12
            $newValue == $newValue->toString();
537 12
        }
538
539 69
        $newInstance = clone $this;
540 69
        $newInstance->string = $newValue;
541
542 69
        return $newInstance;
543
    }
544
545
    //===========================================
546
    // StringConvertibleInterface Implementation
547
    //===========================================
548
549
    /**
550
     * {@inheritDoc}
551
     */
552 16
    public function __toString() {
553 16
        return $this->string;
554
    }
555
556
    /**
557
     * {@inheritDoc}
558
     */
559 74
    public function toString() {
560 74
        return $this->string;
561
    }
562
563
    // =========================
564
    // Countable Implementation
565
    // =========================
566
567
    /**
568
     * {@inheritDoc}
569
     *
570
     * @codeCoverageIgnore
571
     */
572
    public function count() {
573
        return $this->length();
574
    }
575
576
    // =============================
577
    // ArrayIterator Implementation
578
    // =============================
579
580
    /**
581
     * {@inheritDoc}
582
     */
583 4
    public function getIterator() {
584 4
        return new ArrayIterator($this->chars());
585
    }
586
587
    // ==========================================
588
    // ExtensionReceiverInterface Implementation
589
    // ==========================================
590
591
    /**
592
     * {@inheritDoc}
593
     *
594
     * @internal
595
     */
596 5
    public function shouldReceiveType() {
597 5
        return StringObjectExtensionInterface::class;
598
    }
599
600
    /**
601
     * {@inheritDoc}
602
     *
603
     * @internal
604
     */
605 2
    public function getCurrentValue() {
606 2
        return $this->string;
607
    }
608
609
    /**
610
     * {@inheritDoc}
611
     *
612
     * @internal
613
     */
614 2
    public function processResult(array $result) {
615 2
        list($result, $isRaw) = array_values($result);
616
617 2
        if(!$isRaw && is_string($result)) {
618 1
            return $this->createClone($result);
619
        }
620
621 1
        return $result;
622
    }
623
624
}
625