Str::truncate()   D
last analyzed

Complexity

Conditions 10
Paths 20

Size

Total Lines 42
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 1
Metric Value
c 3
b 0
f 1
dl 0
loc 42
rs 4.8196
cc 10
eloc 23
nc 20
nop 4

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * @package    Fuel\Common
4
 * @version    2.0
5
 * @author     Fuel Development Team
6
 * @license    MIT License
7
 * @copyright  2010 - 2015 Fuel Development Team
8
 * @link       http://fuelphp.com
9
 */
10
11
namespace Fuel\Common;
12
13
/**
14
 * String handling with encoding support
15
 *
16
 * PHP needs to be compiled with --enable-mbstring
17
 * or a fallback without encoding support is used
18
 */
19
class Str
20
{
21
	protected $encoding = 'UTF-8';
22
23
  /**
24
	 * Truncates a string to the given length.  It will optionally preserve
25
	 * HTML tags if $is_html is set to true.
26
	 *
27
	 * @param   string  $string        the string to truncate
28
	 * @param   int     $limit         the number of characters to truncate too
29
	 * @param   string  $continuation  the string to use to denote it was truncated
30
	 * @param   bool    $is_html       whether the string has HTML
31
	 * @return  string  the truncated string
32
	 */
33
	public function truncate($string, $limit, $continuation = '...', $is_html = false)
34
	{
35
		$offset = 0;
36
		$tags = array();
37
		if ($is_html)
38
		{
39
			// Handle special characters.
40
			preg_match_all('/&[a-z]+;/i', strip_tags($string), $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER);
41
			foreach ($matches as $match)
0 ignored issues
show
Bug introduced by
The expression $matches of type null|array<integer,array<integer,string>> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
42
			{
43
				if ($match[0][1] >= $limit)
44
				{
45
					break;
46
				}
47
				$limit += ($this->length($match[0][0]) - 1);
48
			}
49
50
			// Handle all the html tags.
51
			preg_match_all('/<[^>]+>([^<]*)/', $string, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER);
52
			foreach ($matches as $match)
0 ignored issues
show
Bug introduced by
The expression $matches of type null|array<integer,array<integer,string>> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
53
			{
54
				if($match[0][1] - $offset >= $limit)
55
				{
56
					break;
57
				}
58
				$tag = $this->sub(strtok($match[0][0], " \t\n\r\0\x0B>"), 1);
59
				if($tag[0] != '/')
60
				{
61
					$tags[] = $tag;
62
				}
63
				elseif (end($tags) == $this->sub($tag, 1))
64
				{
65
					array_pop($tags);
66
				}
67
				$offset += $match[1][1] - $match[0][1];
68
			}
69
		}
70
		$new_string = $this->sub($string, 0, $limit = min($this->length($string),  $limit + $offset));
71
		$new_string .= ($this->length($string) > $limit ? $continuation : '');
72
		$new_string .= (count($tags = array_reverse($tags)) ? '</'.implode('></',$tags).'>' : '');
73
		return $new_string;
74
	}
75
76
	/**
77
	 * Add's _1 to a string or increment the ending number to allow _2, _3, etc
78
	 *
79
	 * @param   string  $str  required
80
	 * @return  string
81
	 */
82
	public function increment($str, $first = 1, $separator = '_')
83
	{
84
		preg_match('/(.+)'.$separator.'([0-9]+)$/', $str, $match);
85
86
		return isset($match[2]) ? $match[1].$separator.($match[2] + 1) : $str.$separator.$first;
87
	}
88
89
	/**
90
	 * Checks wether a string has a precific beginning.
91
	 *
92
	 * @param   string   $str          string to check
93
	 * @param   string   $start        beginning to check for
94
	 * @param   boolean  $ignore_case  wether to ignore the case
95
	 * @return  boolean  wether a string starts with a specified beginning
96
	 */
97
	public function startsWith($str, $start, $ignore_case = false)
98
	{
99
		return (bool) preg_match('/^'.preg_quote($start, '/').'/m'.($ignore_case ? 'i' : ''), $str);
100
	}
101
102
	/**
103
	 * Checks wether a string has a precific ending.
104
	 *
105
	 * @param   string   $str          string to check
106
	 * @param   string   $end          ending to check for
107
	 * @param   boolean  $ignore_case  wether to ignore the case
108
	 * @return  boolean  wether a string ends with a specified ending
109
	 */
110
	public function endsWith($str, $end, $ignore_case = false)
111
	{
112
		return (bool) preg_match('/'.preg_quote($end, '/').'$/m'.($ignore_case ? 'i' : ''), $str);
113
	}
114
115
	/**
116
	 * substr
117
	 *
118
	 * @param   string    $str       required
119
	 * @param   int       $start     required
120
	 * @param   int|null  $length
121
	 * @param   string    $encoding  default UTF-8
122
	 * @return  string
123
	 */
124
	public function sub($str, $start, $length = null, $encoding = null)
125
	{
126
		$encoding or $encoding = $this->encoding;
127
128
		// substr functions don't parse null correctly
129
		$length = is_null($length) ? (function_exists('mb_substr') ? mb_strlen($str, $encoding) : strlen($str)) - $start : $length;
130
131
		return function_exists('mb_substr')
132
			? mb_substr($str, $start, $length, $encoding)
133
			: substr($str, $start, $length);
134
	}
135
136
	/**
137
	 * strlen
138
	 *
139
	 * @param   string  $str       required
140
	 * @param   string  $encoding  default UTF-8
141
	 * @return  int
142
	 */
143
	public function length($str, $encoding = null)
144
	{
145
		$encoding or $encoding = $this->encoding;
146
147
		return function_exists('mb_strlen')
148
			? mb_strlen($str, $encoding)
149
			: strlen($str);
150
	}
151
152
	/**
153
	 * lower
154
	 *
155
	 * @param   string  $str       required
156
	 * @param   string  $encoding  default UTF-8
157
	 * @return  string
158
	 */
159
	public function lower($str, $encoding = null)
160
	{
161
		$encoding or $encoding = $this->encoding;
162
163
		return function_exists('mb_strtolower')
164
			? mb_strtolower($str, $encoding)
165
			: strtolower($str);
166
	}
167
168
	/**
169
	 * upper
170
	 *
171
	 * @param   string  $str       required
172
	 * @param   string  $encoding  default UTF-8
173
	 * @return  string
174
	 */
175
	public function upper($str, $encoding = null)
176
	{
177
		$encoding or $encoding = $this->encoding;
178
179
		return function_exists('mb_strtoupper')
180
			? mb_strtoupper($str, $encoding)
181
			: strtoupper($str);
182
	}
183
184
	/**
185
	 * lcfirst
186
	 *
187
	 * Does not strtoupper first
188
	 *
189
	 * @param   string  $str       required
190
	 * @param   string  $encoding  default UTF-8
191
	 * @return  string
192
	 */
193 View Code Duplication
	public function lcfirst($str, $encoding = null)
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...
194
	{
195
		$encoding or $encoding = $this->encoding;
196
197
		return function_exists('mb_strtolower')
198
			? mb_strtolower(mb_substr($str, 0, 1, $encoding), $encoding).
199
				mb_substr($str, 1, mb_strlen($str, $encoding), $encoding)
200
			: lcfirst($str);
201
	}
202
203
	/**
204
	 * ucfirst
205
	 *
206
	 * Does not strtolower first
207
	 *
208
	 * @param   string $str       required
209
	 * @param   string $encoding  default UTF-8
210
	 * @return   string
211
	 */
212 View Code Duplication
	public function ucfirst($str, $encoding = null)
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...
213
	{
214
		$encoding or $encoding = $this->encoding;
215
216
		return function_exists('mb_strtoupper')
217
			? mb_strtoupper(mb_substr($str, 0, 1, $encoding), $encoding).
218
				mb_substr($str, 1, mb_strlen($str, $encoding), $encoding)
219
			: ucfirst($str);
220
	}
221
222
	/**
223
	 * ucwords
224
	 *
225
	 * First strtolower then ucwords
226
	 *
227
	 * ucwords normally doesn't strtolower first
228
	 * but MB_CASE_TITLE does, so ucwords now too
229
	 *
230
	 * @param   string   $str       required
231
	 * @param   string   $encoding  default UTF-8
232
	 * @return  string
233
	 */
234
	public function ucwords($str, $encoding = null)
235
	{
236
		$encoding or $encoding = $this->encoding;
237
238
		return function_exists('mb_convert_case')
239
			? mb_convert_case($str, MB_CASE_TITLE, $encoding)
240
			: ucwords(strtolower($str));
241
	}
242
243
	/**
244
	  * Creates a random string of characters
245
	  *
246
	  * @param   string  the type of string
247
	  * @param   int     the number of characters
248
	  * @return  string  the random string
249
	  */
250
	public function random($type = 'alnum', $length = 16)
251
	{
252
		switch($type)
253
		{
254
			case 'basic':
255
				$result = mt_rand();
256
				break;
257
258
			case 'unique':
259
				$result = md5(uniqid(mt_rand()));
260
				break;
261
262
			case 'sha1' :
263
				$result = sha1(uniqid(mt_rand(), true));
264
				break;
265
266
			default:
267
			case 'alnum':
0 ignored issues
show
Unused Code introduced by
case 'alnum': does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
268
			case 'numeric':
269
			case 'nozero':
270
			case 'alpha':
271
			case 'distinct':
272
			case 'hexdec':
273
				switch ($type)
274
				{
275
					case 'alpha':
276
						$pool = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
277
						break;
278
279
					case 'numeric':
280
						$pool = '0123456789';
281
						break;
282
283
					case 'nozero':
284
						$pool = '123456789';
285
						break;
286
287
					case 'distinct':
288
						$pool = '2345679ACDEFHJKLMNPRSTUVWXYZ';
289
						break;
290
291
					case 'hexdec':
292
						$pool = '0123456789abcdef';
293
						break;
294
295
					default:
296
					case 'alnum':
0 ignored issues
show
Unused Code introduced by
case 'alnum': $pool ...FGHIJKLMNOPQRSTUVWXYZ'; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
297
						$pool = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
298
				}
299
300
				$result = '';
301
				for ($i=0; $i < $length; $i++)
302
				{
303
					$result .= substr($pool, mt_rand(0, strlen($pool) -1), 1);
304
				}
305
		}
306
307
		return $result;
308
	}
309
310
	/**
311
	 * Returns a closure that will alternate between the args which to return.
312
	 * If you call the closure with false as the arg it will return the value without
313
	 * alternating the next time.
314
	 *
315
	 * @return \Closure
316
	 */
317
	public function alternator()
318
	{
319
		// the args are the values to alternate
320
		$args = func_get_args();
321
322
		return function ($next = true) use ($args)
323
		{
324
			static $i = 0;
325
			return $args[($next ? $i++ : $i) % count($args)];
326
		};
327
	}
328
329
	/**
330
	 * Parse the params from a string using strtr()
331
	 *
332
	 * @param   string  string to parse
333
	 * @param   array   params to str_replace
334
	 * @return  string
335
	 */
336
	public function tr($string, $array = array())
337
	{
338
		if (is_string($string))
339
		{
340
			$tr_arr = array();
341
342
			foreach ($array as $from => $to)
343
			{
344
				substr($from, 0, 1) !== ':' and $from = ':'.$from;
345
				$tr_arr[$from] = $to;
346
			}
347
			unset($array);
348
349
			return strtr($string, $tr_arr);
350
		}
351
		else
352
		{
353
			return $string;
354
		}
355
	}
356
357
	/**
358
	 * Check if a string is json encoded
359
	 *
360
	 * @param  string $string string to check
361
	 * @return bool
362
	 */
363
	public function is_json($string)
364
	{
365
		json_decode($string);
366
		return json_last_error() === JSON_ERROR_NONE;
367
	}
368
369
	/**
370
	 * Check if a string is a valid XML
371
	 *
372
	 * @param  string $string string to check
373
	 * @return bool
374
	 */
375
	public function is_xml($string)
376
	{
377
		$internal_errors = libxml_use_internal_errors();
378
		libxml_use_internal_errors(true);
379
		$result = simplexml_load_string($string) !== false;
380
		libxml_use_internal_errors($internal_errors);
381
382
		return $result;
383
	}
384
385
	/**
386
	 * Check if a string is serialized
387
	 *
388
	 * @param  string $string string to check
389
	 * @return bool
390
	 */
391
	public function is_serialized($string)
392
	{
393
		$array = @unserialize($string);
394
		return ! ($array === false and $string !== 'b:0;');
395
	}
396
397
	/**
398
	 * Check if a string is html
399
	 *
400
	 * @param  string $string string to check
401
	 * @return bool
402
	 */
403
	public function is_html($string)
404
	{
405
		return strlen(strip_tags($string)) < strlen($string);
406
	}
407
}
408