1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Spatie\String; |
4
|
|
|
|
5
|
|
|
use ArrayAccess; |
6
|
|
|
use Spatie\String\Integrations\Underscore; |
7
|
|
|
use Spatie\String\Exceptions\UnsetOffsetException; |
8
|
|
|
use Spatie\String\Exceptions\UnknownFunctionException; |
9
|
|
|
use Spatie\String\Exceptions\ErrorCreatingStringException; |
10
|
|
|
|
11
|
|
|
/** |
12
|
|
|
* Magic methods provided by underscore are documented here. |
13
|
|
|
* |
14
|
|
|
* @see \Underscore\Methods\StringsMethods |
15
|
|
|
* |
16
|
|
|
* @method \Spatie\String\Str accord($count, $many, $one, $zero = null) |
17
|
|
|
* @method \Spatie\String\Str random($length = 16) |
18
|
|
|
* @method \Spatie\String\Str quickRandom($length = 16) |
19
|
|
|
* @method randomStrings($words, $length = 10) |
20
|
|
|
* @method bool endsWith($needles) |
21
|
|
|
* @method bool isIp() |
22
|
|
|
* @method bool isEmail() |
23
|
|
|
* @method bool isUrl() |
24
|
|
|
* @method bool startsWith() |
25
|
|
|
* @method bool find($needle, $caseSensitive = false, $absolute = false) |
26
|
|
|
* @method array slice($slice) |
27
|
|
|
* @method \Spatie\String\Str sliceFrom($slice) |
28
|
|
|
* @method \Spatie\String\Str sliceTo($slice) |
29
|
|
|
* @method \Spatie\String\Str baseClass() |
30
|
|
|
* @method \Spatie\String\Str prepend($with) |
31
|
|
|
* @method \Spatie\String\Str append($with) |
32
|
|
|
* @method \Spatie\String\Str limit($limit = 100, $end = '...') |
33
|
|
|
* @method \Spatie\String\Str remove($remove) |
34
|
|
|
* @method \Spatie\String\Str replace($replace, $with) |
35
|
|
|
* @method \Spatie\String\Str toggle($first, $second, $loose = false) |
36
|
|
|
* @method \Spatie\String\Str slugify($separator = '-') |
37
|
|
|
* @method array explode($with, $limit = null) |
38
|
|
|
* @method \Spatie\String\Str lower() |
39
|
|
|
* @method \Spatie\String\Str plural() |
40
|
|
|
* @method \Spatie\String\Str singular() |
41
|
|
|
* @method \Spatie\String\Str upper() |
42
|
|
|
* @method \Spatie\String\Str title() |
43
|
|
|
* @method \Spatie\String\Str words($words = 100, $end = '...') |
44
|
|
|
* @method \Spatie\String\Str toPascalCase() |
45
|
|
|
* @method \Spatie\String\Str toSnakeCase() |
46
|
|
|
* @method \Spatie\String\Str toCamelCase() |
47
|
|
|
*/ |
48
|
|
|
class Str implements ArrayAccess |
49
|
|
|
{ |
50
|
|
|
/** |
51
|
|
|
* @var string |
52
|
|
|
*/ |
53
|
|
|
protected $string; |
54
|
|
|
|
55
|
|
|
/** |
56
|
|
|
* @param string $string |
57
|
|
|
*/ |
58
|
|
|
public function __construct($string = '') |
59
|
|
|
{ |
60
|
|
|
if (is_array($string)) { |
61
|
|
|
throw new ErrorCreatingStringException('Can\'t create string from an array'); |
62
|
|
|
} |
63
|
|
|
|
64
|
|
|
if (is_object($string) && ! method_exists($string, '__toString')) { |
65
|
|
|
throw new ErrorCreatingStringException( |
66
|
|
|
'Can\'t create string from an object that doesn\'t implement __toString' |
67
|
|
|
); |
68
|
|
|
} |
69
|
|
|
|
70
|
|
|
$this->string = (string) $string; |
71
|
|
|
} |
72
|
|
|
|
73
|
|
|
/** |
74
|
|
|
* @return string |
75
|
|
|
*/ |
76
|
|
|
public function __toString() |
77
|
|
|
{ |
78
|
|
|
return $this->string; |
79
|
|
|
} |
80
|
|
|
|
81
|
|
|
/** |
82
|
|
|
* Get the string between the given start and end. |
83
|
|
|
* |
84
|
|
|
* @param $start |
85
|
|
|
* @param $end |
86
|
|
|
* |
87
|
|
|
* @return \Spatie\String\Str |
88
|
|
|
*/ |
89
|
|
|
public function between($start, $end) |
90
|
|
|
{ |
91
|
|
|
if ($start == '' && $end == '') { |
92
|
|
|
return $this; |
93
|
|
|
} |
94
|
|
|
|
95
|
|
|
if ($start != '' && strpos($this->string, $start) === false) { |
96
|
|
|
return new static(); |
97
|
|
|
} |
98
|
|
|
|
99
|
|
|
if ($end != '' && strpos($this->string, $end) === false) { |
100
|
|
|
return new static(); |
101
|
|
|
} |
102
|
|
|
|
103
|
|
View Code Duplication |
if ($start == '') { |
104
|
|
|
return new static(substr($this->string, 0, strpos($this->string, $end))); |
105
|
|
|
} |
106
|
|
|
|
107
|
|
View Code Duplication |
if ($end == '') { |
108
|
|
|
return new static(substr($this->string, strpos($this->string, $start) + strlen($start))); |
109
|
|
|
} |
110
|
|
|
|
111
|
|
|
$stringWithoutStart = explode($start, $this->string)[1]; |
112
|
|
|
|
113
|
|
|
$middle = explode($end, $stringWithoutStart)[0]; |
114
|
|
|
|
115
|
|
|
return new static($middle); |
116
|
|
|
} |
117
|
|
|
|
118
|
|
|
/** |
119
|
|
|
* Convert the string to uppercase. |
120
|
|
|
* |
121
|
|
|
* @return \Spatie\String\Str |
122
|
|
|
*/ |
123
|
|
|
public function toUpper() |
124
|
|
|
{ |
125
|
|
|
return new static(strtoupper($this->string)); |
126
|
|
|
} |
127
|
|
|
|
128
|
|
|
/** |
129
|
|
|
* Convert the string to lowercase. |
130
|
|
|
* |
131
|
|
|
* @return \Spatie\String\Str |
132
|
|
|
*/ |
133
|
|
|
public function toLower() |
134
|
|
|
{ |
135
|
|
|
return new static(strtolower($this->string)); |
136
|
|
|
} |
137
|
|
|
|
138
|
|
|
/** |
139
|
|
|
* Shortens a string in a pretty way. It will clean it by trimming |
140
|
|
|
* it, remove all double spaces and html. If the string is then still |
141
|
|
|
* longer than the specified $length it will be shortened. The end |
142
|
|
|
* of the string is always a full word concatinated with the |
143
|
|
|
* specified moreTextIndicator. |
144
|
|
|
* |
145
|
|
|
* @param int $length |
146
|
|
|
* @param string $moreTextIndicator |
147
|
|
|
* |
148
|
|
|
* @return \Spatie\String\Str |
149
|
|
|
*/ |
150
|
|
|
public function tease($length = 200, $moreTextIndicator = '...') |
151
|
|
|
{ |
152
|
|
|
$sanitizedString = $this->sanitizeForTease($this->string); |
153
|
|
|
|
154
|
|
|
if (strlen($sanitizedString) == 0) { |
155
|
|
|
return new static(); |
156
|
|
|
} |
157
|
|
|
|
158
|
|
|
if (strlen($sanitizedString) <= $length) { |
159
|
|
|
return new static($sanitizedString); |
160
|
|
|
} |
161
|
|
|
|
162
|
|
|
$ww = wordwrap($sanitizedString, $length, "\n"); |
163
|
|
|
$shortenedString = substr($ww, 0, strpos($ww, "\n")).$moreTextIndicator; |
164
|
|
|
|
165
|
|
|
return new static($shortenedString); |
166
|
|
|
} |
167
|
|
|
|
168
|
|
|
/** |
169
|
|
|
* Sanitize the string for teasing. |
170
|
|
|
* |
171
|
|
|
* @param $string |
172
|
|
|
* |
173
|
|
|
* @return string |
174
|
|
|
*/ |
175
|
|
|
private function sanitizeForTease($string) |
176
|
|
|
{ |
177
|
|
|
$string = trim($string); |
178
|
|
|
|
179
|
|
|
//remove html |
180
|
|
|
$string = strip_tags($string); |
181
|
|
|
|
182
|
|
|
//replace multiple spaces |
183
|
|
|
$string = preg_replace("/\s+/", ' ', $string); |
184
|
|
|
|
185
|
|
|
return $string; |
186
|
|
|
} |
187
|
|
|
|
188
|
|
|
/** |
189
|
|
|
* Replace the first occurrence of a string. |
190
|
|
|
* |
191
|
|
|
* @param $search |
192
|
|
|
* @param $replace |
193
|
|
|
* |
194
|
|
|
* @return \Spatie\String\Str |
195
|
|
|
*/ |
196
|
|
View Code Duplication |
public function replaceFirst($search, $replace) |
|
|
|
|
197
|
|
|
{ |
198
|
|
|
if ($search == '') { |
199
|
|
|
return $this; |
200
|
|
|
} |
201
|
|
|
|
202
|
|
|
$position = strpos($this->string, $search); |
203
|
|
|
|
204
|
|
|
if ($position === false) { |
205
|
|
|
return $this; |
206
|
|
|
} |
207
|
|
|
|
208
|
|
|
$resultString = substr_replace($this->string, $replace, $position, strlen($search)); |
209
|
|
|
|
210
|
|
|
return new static($resultString); |
211
|
|
|
} |
212
|
|
|
|
213
|
|
|
/** |
214
|
|
|
* Replace the last occurrence of a string. |
215
|
|
|
* |
216
|
|
|
* @param $search |
217
|
|
|
* @param $replace |
218
|
|
|
* |
219
|
|
|
* @return \Spatie\String\Str |
220
|
|
|
*/ |
221
|
|
View Code Duplication |
public function replaceLast($search, $replace) |
|
|
|
|
222
|
|
|
{ |
223
|
|
|
if ($search == '') { |
224
|
|
|
return $this; |
225
|
|
|
} |
226
|
|
|
|
227
|
|
|
$position = strrpos($this->string, $search); |
228
|
|
|
|
229
|
|
|
if ($position === false) { |
230
|
|
|
return $this; |
231
|
|
|
} |
232
|
|
|
|
233
|
|
|
$resultString = substr_replace($this->string, $replace, $position, strlen($search)); |
234
|
|
|
|
235
|
|
|
return new static($resultString); |
236
|
|
|
} |
237
|
|
|
|
238
|
|
|
/** |
239
|
|
|
* Prefix a string. |
240
|
|
|
* |
241
|
|
|
* @param $string |
242
|
|
|
* |
243
|
|
|
* @return \Spatie\String\Str |
244
|
|
|
*/ |
245
|
|
|
public function prefix($string) |
246
|
|
|
{ |
247
|
|
|
return new static($string.$this->string); |
248
|
|
|
} |
249
|
|
|
|
250
|
|
|
/** |
251
|
|
|
* Suffix a string. |
252
|
|
|
* |
253
|
|
|
* @param $string |
254
|
|
|
* |
255
|
|
|
* @return \Spatie\String\Str |
256
|
|
|
*/ |
257
|
|
|
public function suffix($string) |
258
|
|
|
{ |
259
|
|
|
return new static($this->string.$string); |
260
|
|
|
} |
261
|
|
|
|
262
|
|
|
/** |
263
|
|
|
* Concatenate a string. |
264
|
|
|
* |
265
|
|
|
* @param $string |
266
|
|
|
* |
267
|
|
|
* @return \Spatie\String\Str |
268
|
|
|
*/ |
269
|
|
|
public function concat($string) |
270
|
|
|
{ |
271
|
|
|
return $this->suffix($string); |
272
|
|
|
} |
273
|
|
|
|
274
|
|
|
/** |
275
|
|
|
* Get the possessive version of a string. |
276
|
|
|
* |
277
|
|
|
* @return \Spatie\String\Str |
278
|
|
|
*/ |
279
|
|
|
public function possessive() |
280
|
|
|
{ |
281
|
|
|
if ($this->string == '') { |
282
|
|
|
return new static(); |
283
|
|
|
} |
284
|
|
|
|
285
|
|
|
$noApostropheEdgeCases = ['it']; |
286
|
|
|
|
287
|
|
|
if (in_array($this->string, $noApostropheEdgeCases)) { |
288
|
|
|
return new static($this->string.'s'); |
289
|
|
|
} |
290
|
|
|
|
291
|
|
|
return new static($this->string.'\''.($this->string[strlen($this->string) - 1] != 's' ? 's' : '')); |
292
|
|
|
} |
293
|
|
|
|
294
|
|
|
/** |
295
|
|
|
* Get a segment from a string based on a delimiter. |
296
|
|
|
* Returns an empty string when the offset doesn't exist. |
297
|
|
|
* Use a negative index to start counting from the last element. |
298
|
|
|
* |
299
|
|
|
* @param string $delimiter |
300
|
|
|
* @param int $index |
301
|
|
|
* |
302
|
|
|
* @return \Spatie\String\Str |
303
|
|
|
*/ |
304
|
|
|
public function segment($delimiter, $index) |
305
|
|
|
{ |
306
|
|
|
$segments = explode($delimiter, $this->string); |
307
|
|
|
|
308
|
|
|
if ($index < 0) { |
309
|
|
|
$segments = array_reverse($segments); |
310
|
|
|
$index = abs($index) - 1; |
311
|
|
|
} |
312
|
|
|
|
313
|
|
|
$segment = isset($segments[$index]) ? $segments[$index] : ''; |
314
|
|
|
|
315
|
|
|
return new static($segment); |
316
|
|
|
} |
317
|
|
|
|
318
|
|
|
/** |
319
|
|
|
* Get the first segment from a string based on a delimiter. |
320
|
|
|
* |
321
|
|
|
* @param string $delimiter |
322
|
|
|
* |
323
|
|
|
* @return \Spatie\String\Str |
324
|
|
|
*/ |
325
|
|
|
public function firstSegment($delimiter) |
326
|
|
|
{ |
327
|
|
|
return (new static($this->string))->segment($delimiter, 0); |
328
|
|
|
} |
329
|
|
|
|
330
|
|
|
/** |
331
|
|
|
* Get the last segment from a string based on a delimiter. |
332
|
|
|
* |
333
|
|
|
* @param string $delimiter |
334
|
|
|
* |
335
|
|
|
* @return \Spatie\String\Str |
336
|
|
|
*/ |
337
|
|
|
public function lastSegment($delimiter) |
338
|
|
|
{ |
339
|
|
|
return (new static($this->string))->segment($delimiter, -1); |
340
|
|
|
} |
341
|
|
|
|
342
|
|
|
/** |
343
|
|
|
* Pop (remove) the last segment of a string based on a delimiter. |
344
|
|
|
* |
345
|
|
|
* @param string $delimiter |
346
|
|
|
* |
347
|
|
|
* @return \Spatie\String\Str |
348
|
|
|
*/ |
349
|
|
|
public function pop($delimiter) |
350
|
|
|
{ |
351
|
|
|
return (new static($this->string))->replaceLast($delimiter.$this->lastSegment($delimiter), ''); |
352
|
|
|
} |
353
|
|
|
|
354
|
|
|
/** |
355
|
|
|
* Strip whitespace (or other characters) from the beginning and end of a string. |
356
|
|
|
* |
357
|
|
|
* @param string $characterMask |
358
|
|
|
* |
359
|
|
|
* @return \Spatie\String\Str |
360
|
|
|
*/ |
361
|
|
|
public function trim($characterMask = " \t\n\r\0\x0B") |
362
|
|
|
{ |
363
|
|
|
return new static(trim($this->string, $characterMask)); |
364
|
|
|
} |
365
|
|
|
|
366
|
|
|
/** |
367
|
|
|
* Alias for find. |
368
|
|
|
* |
369
|
|
|
* @param array|string $needle |
370
|
|
|
* @param bool $caseSensitive |
371
|
|
|
* @param bool $absolute |
372
|
|
|
* |
373
|
|
|
* @return bool |
374
|
|
|
*/ |
375
|
|
|
public function contains($needle, $caseSensitive = false, $absolute = false) |
376
|
|
|
{ |
377
|
|
|
return $this->find($needle, $caseSensitive, $absolute); |
378
|
|
|
} |
379
|
|
|
|
380
|
|
|
/** |
381
|
|
|
* Unknown methods calls will be handled by various integrations. |
382
|
|
|
* |
383
|
|
|
* @param $method |
384
|
|
|
* @param $args |
385
|
|
|
* |
386
|
|
|
* @throws UnknownFunctionException |
387
|
|
|
* |
388
|
|
|
* @return mixed|\Spatie\String\Str |
389
|
|
|
*/ |
390
|
|
|
public function __call($method, $args) |
391
|
|
|
{ |
392
|
|
|
$underscore = new Underscore(); |
393
|
|
|
|
394
|
|
|
if ($underscore->isSupportedMethod($method)) { |
395
|
|
|
return $underscore->call($this, $method, $args); |
396
|
|
|
} |
397
|
|
|
|
398
|
|
|
throw new UnknownFunctionException(sprintf('String function %s does not exist', $method)); |
399
|
|
|
} |
400
|
|
|
|
401
|
|
|
/** |
402
|
|
|
* Whether a offset exists. |
403
|
|
|
* |
404
|
|
|
* @link http://php.net/manual/en/arrayaccess.offsetexists.php |
405
|
|
|
* |
406
|
|
|
* @param mixed $offset An offset to check for. |
407
|
|
|
* |
408
|
|
|
* @return bool true on success or false on failure. |
409
|
|
|
* The return value will be casted to boolean if non-boolean was returned. |
410
|
|
|
*/ |
411
|
|
|
public function offsetExists($offset) |
412
|
|
|
{ |
413
|
|
|
return strlen($this->string) >= ($offset + 1); |
414
|
|
|
} |
415
|
|
|
|
416
|
|
|
/** |
417
|
|
|
* Offset to retrieve. |
418
|
|
|
* |
419
|
|
|
* @link http://php.net/manual/en/arrayaccess.offsetget.php |
420
|
|
|
* |
421
|
|
|
* @param mixed $offset The offset to retrieve. |
422
|
|
|
* |
423
|
|
|
* @return mixed Can return all value types. |
424
|
|
|
*/ |
425
|
|
|
public function offsetGet($offset) |
426
|
|
|
{ |
427
|
|
|
$character = substr($this->string, $offset, 1); |
428
|
|
|
|
429
|
|
|
return new static($character ?: ''); |
430
|
|
|
} |
431
|
|
|
|
432
|
|
|
/** |
433
|
|
|
* Offset to set. |
434
|
|
|
* |
435
|
|
|
* @link http://php.net/manual/en/arrayaccess.offsetset.php |
436
|
|
|
* |
437
|
|
|
* @param mixed $offset The offset to assign the value to. |
438
|
|
|
* @param mixed $value The value to set. |
439
|
|
|
*/ |
440
|
|
|
public function offsetSet($offset, $value) |
441
|
|
|
{ |
442
|
|
|
$this->string[$offset] = $value; |
443
|
|
|
} |
444
|
|
|
|
445
|
|
|
/** |
446
|
|
|
* Offset to unset. |
447
|
|
|
* |
448
|
|
|
* @link http://php.net/manual/en/arrayaccess.offsetunset.php |
449
|
|
|
* |
450
|
|
|
* @param mixed $offset The offset to unset. |
451
|
|
|
* |
452
|
|
|
* @throws UnsetOffsetException |
453
|
|
|
*/ |
454
|
|
|
public function offsetUnset($offset) |
455
|
|
|
{ |
456
|
|
|
throw new UnsetOffsetException(); |
457
|
|
|
} |
458
|
|
|
} |
459
|
|
|
|
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.