<p>I have a code snippet written in PHP that pulls a block of text from a database and sends it out to a widget on a webpage.  The original block of text can be a lengthy article or a short sentence or two; but for this widget I can't display more than, say, 200 characters.  I could use substr() to chop off the text at 200 chars, but the result would be cutting off in the middle of words-- what I really want is to chop the text at the end of the last <i>word</i> before 200 chars.</p>

[{'answer_id': 79986, 'author': 'Grey Panther', 'author_id': 1265, 'author_profile': 'https://Stackoverflow.com/users/1265', 'pm_score': 9, 'selected': True, 'text': '<p>By using the <a href="http://www.php.net/wordwrap" rel="noreferrer">wordwrap</a> function. It splits the texts in multiple lines such that the maximum width is the one you specified, breaking at word boundaries. After splitting, you simply take the first line:</p>\n\n<pre><code>substr($string, 0, strpos(wordwrap($string, $your_desired_width), "\\n"));\n</code></pre>\n\n<p>One thing this oneliner doesn\'t handle is the case when the text itself is shorter than the desired width. To handle this edge-case, one should do something like:</p>\n\n<pre><code>if (strlen($string) &gt; $your_desired_width) \n{\n    $string = wordwrap($string, $your_desired_width);\n    $string = substr($string, 0, strpos($string, "\\n"));\n}\n</code></pre>\n\n<hr>\n\n<p>The above solution has the problem of prematurely cutting the text if it contains a newline before the actual cutpoint. Here a version which solves this problem:</p>\n\n<pre><code>function tokenTruncate($string, $your_desired_width) {\n  $parts = preg_split(\'/([\\s\\n\\r]+)/\', $string, null, PREG_SPLIT_DELIM_CAPTURE);\n  $parts_count = count($parts);\n\n  $length = 0;\n  $last_part = 0;\n  for (; $last_part &lt; $parts_count; ++$last_part) {\n    $length += strlen($parts[$last_part]);\n    if ($length &gt; $your_desired_width) { break; }\n  }\n\n  return implode(array_slice($parts, 0, $last_part));\n}\n</code></pre>\n\n<p>Also, here is the PHPUnit testclass used to test the implementation:</p>\n\n<pre><code>class TokenTruncateTest extends PHPUnit_Framework_TestCase {\n  public function testBasic() {\n    $this-&gt;assertEquals("1 3 5 7 9 ",\n      tokenTruncate("1 3 5 7 9 11 14", 10));\n  }\n\n  public function testEmptyString() {\n    $this-&gt;assertEquals("",\n      tokenTruncate("", 10));\n  }\n\n  public function testShortString() {\n    $this-&gt;assertEquals("1 3",\n      tokenTruncate("1 3", 10));\n  }\n\n  public function testStringTooLong() {\n    $this-&gt;assertEquals("",\n      tokenTruncate("toooooooooooolooooong", 10));\n  }\n\n  public function testContainingNewline() {\n    $this-&gt;assertEquals("1 3\\n5 7 9 ",\n      tokenTruncate("1 3\\n5 7 9 11 14", 10));\n  }\n}\n</code></pre>\n\n<h1><strong>EDIT :</strong></h1>\n\n<p>Special UTF8 characters like \'à\' are not handled. Add \'u\' at the end of the REGEX to handle it:</p>\n\n<p><code>$parts = preg_split(\'/([\\s\\n\\r]+)/u\', $string, null, PREG_SPLIT_DELIM_CAPTURE);</code></p>\n'}, {'answer_id': 80001, 'author': 'Lucas Oman', 'author_id': 6726, 'author_profile': 'https://Stackoverflow.com/users/6726', 'pm_score': 3, 'selected': False, 'text': '<p>Use strpos and substr:</p>\n\n<pre><code>&lt;?php\n\n$longString = "I have a code snippet written in PHP that pulls a block of text.";\n$truncated = substr($longString,0,strpos($longString,\' \',30));\n\necho $truncated;\n</code></pre>\n\n<p>This will give you a string truncated at the first space after 30 characters.</p>\n'}, {'answer_id': 80014, 'author': 'UnkwnTech', 'author_id': 115, 'author_profile': 'https://Stackoverflow.com/users/115', 'pm_score': 3, 'selected': False, 'text': "<p>Here you go:</p>\n\n<pre><code>function neat_trim($str, $n, $delim='…') {\n   $len = strlen($str);\n   if ($len &gt; $n) {\n       preg_match('/(.{' . $n . '}.*?)\\b/', $str, $matches);\n       return rtrim($matches[1]) . $delim;\n   }\n   else {\n       return $str;\n   }\n}\n</code></pre>\n"}, {'answer_id': 80030, 'author': 'Justin Poliey', 'author_id': 6967, 'author_profile': 'https://Stackoverflow.com/users/6967', 'pm_score': 2, 'selected': False, 'text': '<p>I would use the preg_match function to do this, as what you want is a pretty simple expression.</p>\n\n<pre><code>$matches = array();\n$result = preg_match("/^(.{1,199})[\\s]/i", $text, $matches);\n</code></pre>\n\n<p>The expression means "match any substring starting from the beginning of length 1-200 that ends with a space." The result is in $result, and the match is in $matches. That takes care of your original question, which is specifically ending on any space. If you want to make it end on newlines, change the regular expression to:</p>\n\n<pre><code>$result = preg_match("/^(.{1,199})[\\n]/i", $text, $matches);\n</code></pre>\n'}, {'answer_id': 80066, 'author': 'mattmac', 'author_id': 14935, 'author_profile': 'https://Stackoverflow.com/users/14935', 'pm_score': 7, 'selected': False, 'text': "<p>This will return the first 200 characters of words:</p>\n\n<pre><code>preg_replace('/\\s+?(\\S+)?$/', '', substr($string, 0, 201));\n</code></pre>\n"}, {'answer_id': 80450, 'author': 'Garrett Albright', 'author_id': 11023, 'author_profile': 'https://Stackoverflow.com/users/11023', 'pm_score': 3, 'selected': False, 'text': '<p>Keep in mind whenever you\'re splitting by "word" anywhere that some languages such as Chinese and Japanese do not use a space character to split words. Also, a malicious user could simply enter text without any spaces, or using some Unicode look-alike to the standard space character, in which case any solution you use may end up displaying the entire text anyway. A way around this may be to check the string length after splitting it on spaces as normal, then, if the string is still above an abnormal limit - maybe 225 characters in this case - going ahead and splitting it dumbly at that limit.</p>\n\n<p>One more caveat with things like this when it comes to non-ASCII characters; strings containing them may be interpreted by PHP\'s standard strlen() as being longer than they really are, because a single character may take two or more bytes instead of just one. If you just use the strlen()/substr() functions to split strings, you may split a string in the middle of a character! When in doubt, <a href="http://us3.php.net/manual/en/function.mb-strlen.php" rel="nofollow noreferrer">mb_strlen()</a>/<a href="http://php.net/mb_substr" rel="nofollow noreferrer">mb_substr()</a> are a little more foolproof.</p>\n'}, {'answer_id': 2523223, 'author': 'Camsoft', 'author_id': 248848, 'author_profile': 'https://Stackoverflow.com/users/248848', 'pm_score': 3, 'selected': False, 'text': '<p>Here is my function based on @Cd-MaN\'s approach.</p>\n\n<pre><code>function shorten($string, $width) {\n  if(strlen($string) &gt; $width) {\n    $string = wordwrap($string, $width);\n    $string = substr($string, 0, strpos($string, "\\n"));\n  }\n\n  return $string;\n}\n</code></pre>\n'}, {'answer_id': 4400574, 'author': 'amateur barista', 'author_id': 467453, 'author_profile': 'https://Stackoverflow.com/users/467453', 'pm_score': 1, 'selected': False, 'text': '<p>Based on @Justin Poliey\'s regex:</p>\n\n<pre><code>// Trim very long text to 120 characters. Add an ellipsis if the text is trimmed.\nif(strlen($very_long_text) &gt; 120) {\n  $matches = array();\n  preg_match("/^(.{1,120})[\\s]/i", $very_long_text, $matches);\n  $trimmed_text = $matches[0]. \'...\';\n}\n</code></pre>\n'}, {'answer_id': 4665347, 'author': 'Dave', 'author_id': 382927, 'author_profile': 'https://Stackoverflow.com/users/382927', 'pm_score': 6, 'selected': False, 'text': "<pre><code>$WidgetText = substr($string, 0, strrpos(substr($string, 0, 200), ' '));\n</code></pre>\n\n<p>And there you have it — a reliable method of truncating any string to the nearest whole word, while staying under the maximum string length.</p>\n\n<p>I've tried the other examples above and they did not produce the desired results.</p>\n"}, {'answer_id': 7904269, 'author': 'Yo-L', 'author_id': 310108, 'author_profile': 'https://Stackoverflow.com/users/310108', 'pm_score': 2, 'selected': False, 'text': '<p>Ok so I got another version of this based on the above answers but taking more things in account(utf-8, \\n and &amp;nbsp ; ), also a line stripping the wordpress shortcodes commented if used with wp.</p>\n\n<pre><code>function neatest_trim($content, $chars) \n  if (strlen($content) &gt; $chars) \n  {\n    $content = str_replace(\'&amp;nbsp;\', \' \', $content);\n    $content = str_replace("\\n", \'\', $content);\n    // use with wordpress    \n    //$content = strip_tags(strip_shortcodes(trim($content)));\n    $content = strip_tags(trim($content));\n    $content = preg_replace(\'/\\s+?(\\S+)?$/\', \'\', mb_substr($content, 0, $chars));\n\n    $content = trim($content) . \'...\';\n    return $content;\n  }\n</code></pre>\n'}, {'answer_id': 8072672, 'author': 'tanc', 'author_id': 1037075, 'author_profile': 'https://Stackoverflow.com/users/1037075', 'pm_score': 2, 'selected': False, 'text': "<p>This is a small fix for mattmac's answer:</p>\n\n<pre><code>preg_replace('/\\s+?(\\S+)?$/', '', substr($string . ' ', 0, 201));\n</code></pre>\n\n<p>The only difference is to add a space at the end of $string. This ensures the last word isn't cut off as per ReX357's comment.</p>\n\n<p>I don't have enough rep points to add this as a comment.</p>\n"}, {'answer_id': 10026115, 'author': 'Bud Damyanov', 'author_id': 632524, 'author_profile': 'https://Stackoverflow.com/users/632524', 'pm_score': 2, 'selected': False, 'text': '<pre><code>/*\nCut the string without breaking any words, UTF-8 aware \n* param string $str The text string to split\n* param integer $start The start position, defaults to 0\n* param integer $words The number of words to extract, defaults to 15\n*/\nfunction wordCutString($str, $start = 0, $words = 15 ) {\n    $arr = preg_split("/[\\s]+/",  $str, $words+1);\n    $arr = array_slice($arr, $start, $words);\n    return join(\' \', $arr);\n}\n</code></pre>\n\n<p>Usage:</p>\n\n<pre><code>$input = \'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna liqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\';\necho wordCutString($input, 0, 10); \n</code></pre>\n\n<p>This will output first 10 words.</p>\n\n<p>The <code>preg_split</code> function is used to split a string into substrings. The boundaries along which the string is to be split, are specified using a regular expressions pattern.</p>\n\n<p><code>preg_split</code> function takes 4 parameters, but only the first 3 are relevant to us right now.</p>\n\n<p>First Parameter – Pattern\nThe first parameter is the regular expressions pattern along which the string is to be split. In our case, we want to split the string across word boundaries. Therefore we use a predefined character class <code>\\s</code> which matches white space characters such as space, tab, carriage return and line feed.</p>\n\n<p>Second Parameter – Input String\nThe second parameter is the long text string which we want to split.</p>\n\n<p>Third Parameter – Limit\nThe third parameter specifies the number of substrings which should be returned. If you set the limit to <code>n</code>, preg_split will return an array of n elements. The first <code>n-1</code> elements will contain the substrings. The last <code>(n th)</code> element will contain the rest of the string.</p>\n'}, {'answer_id': 15089518, 'author': 'gosukiwi', 'author_id': 1015566, 'author_profile': 'https://Stackoverflow.com/users/1015566', 'pm_score': 0, 'selected': False, 'text': '<p>I know this is old, but...</p>\n\n<pre><code>function _truncate($str, $limit) {\n    if(strlen($str) &lt; $limit)\n        return $str;\n    $uid = uniqid();\n    return array_shift(explode($uid, wordwrap($str, $limit, $uid)));\n}\n</code></pre>\n'}, {'answer_id': 17852480, 'author': 'Sergiy Sokolenko', 'author_id': 131337, 'author_profile': 'https://Stackoverflow.com/users/131337', 'pm_score': 5, 'selected': False, 'text': '<p>The following solution was born when I\'ve noticed a $break parameter of <a href="http://php.net/wordwrap" rel="noreferrer">wordwrap</a> function:</p>\n\n<blockquote>\n  <p>string wordwrap ( string $str [, int $width = 75 [, string $break =\n  "\\n" [, bool $cut = false ]]] )</p>\n</blockquote>\n\n<p>Here is <strong>the solution</strong>:</p>\n\n<pre><code>/**\n * Truncates the given string at the specified length.\n *\n * @param string $str The input string.\n * @param int $width The number of chars at which the string will be truncated.\n * @return string\n */\nfunction truncate($str, $width) {\n    return strtok(wordwrap($str, $width, "...\\n"), "\\n");\n}\n</code></pre>\n\n<p><strong>Example #1.</strong></p>\n\n<pre><code>print truncate("This is very long string with many chars.", 25);\n</code></pre>\n\n<p>The above example will output:</p>\n\n<pre><code>This is very long string...\n</code></pre>\n\n<p><strong>Example #2.</strong></p>\n\n<pre><code>print truncate("This is short string.", 25);\n</code></pre>\n\n<p>The above example will output:</p>\n\n<pre><code>This is short string.\n</code></pre>\n'}, {'answer_id': 21659546, 'author': 'Yousef Altaf', 'author_id': 454012, 'author_profile': 'https://Stackoverflow.com/users/454012', 'pm_score': -1, 'selected': False, 'text': '<p>I used this before</p>\n\n<pre><code>&lt;?php\n    $your_desired_width = 200;\n    $string = $var-&gt;content;\n    if (strlen($string) &gt; $your_desired_width) {\n        $string = wordwrap($string, $your_desired_width);\n        $string = substr($string, 0, strpos($string, "\\n")) . " More...";\n    }\n    echo $string;\n?&gt;\n</code></pre>\n'}, {'answer_id': 22783274, 'author': 'slash3b', 'author_id': 3478120, 'author_profile': 'https://Stackoverflow.com/users/3478120', 'pm_score': -1, 'selected': False, 'text': '<p>May be this will help someone:</p>\n\n<pre><code>&lt;?php\n\n    $string = "Your line of text";\n    $spl = preg_match("/([, \\.\\d\\-\'\'\\"\\"_()]*\\w+[, \\.\\d\\-\'\'\\"\\"_()]*){50}/", $string, $matches);\n    if (isset($matches[0])) {\n        $matches[0] .= "...";\n        echo "&lt;br /&gt;" . $matches[0];\n    } else {\n        echo "&lt;br /&gt;" . $string;\n    }\n\n?&gt;\n</code></pre>\n'}, {'answer_id': 24204404, 'author': 'Rikudou_Sennin', 'author_id': 3601208, 'author_profile': 'https://Stackoverflow.com/users/3601208', 'pm_score': 1, 'selected': False, 'text': '<p>I have a function that does almost what you want, if you\'ll do a few edits, it will fit exactly:</p>\n\n<pre><code>&lt;?php\nfunction stripByWords($string,$length,$delimiter = \'&lt;br&gt;\') {\n    $words_array = explode(" ",$string);\n    $strlen = 0;\n    $return = \'\';\n    foreach($words_array as $word) {\n        $strlen += mb_strlen($word,\'utf8\');\n        $return .= $word." ";\n        if($strlen &gt;= $length) {\n            $strlen = 0;\n            $return .= $delimiter;\n        }\n    }\n    return $return;\n}\n?&gt;\n</code></pre>\n'}, {'answer_id': 24557257, 'author': 'Artem P', 'author_id': 712308, 'author_profile': 'https://Stackoverflow.com/users/712308', 'pm_score': 3, 'selected': False, 'text': '<pre><code>$shorttext = preg_replace(\'/^([\\s\\S]{1,200})[\\s]+?[\\s\\S]+/\', \'$1\', $fulltext);\n</code></pre>\n\n<p>Description:</p>\n\n<ul>\n<li><code>^</code> - start from beginning of string</li>\n<li><code>([\\s\\S]{1,200})</code> - get from 1 to 200 of any character</li>\n<li><code>[\\s]+?</code> - not include spaces at the end of short text so we can avoid <code>word ...</code> instead of <code>word...</code></li>\n<li><code>[\\s\\S]+</code> - match all other content</li>\n</ul>\n\n<p>Tests:</p>\n\n<ol>\n<li><a href="http://regex101.com/r/bV7dJ6/3" rel="nofollow noreferrer"><code>regex101.com</code></a> let\'s add to <code>or</code> few other <code>r</code></li>\n<li><a href="http://regex101.com/r/bV7dJ6/4" rel="nofollow noreferrer"><code>regex101.com</code></a> <code>orrrr</code> exactly 200 characters.</li>\n<li><a href="http://regex101.com/r/bV7dJ6/5" rel="nofollow noreferrer"><code>regex101.com</code></a> after fifth <code>r</code> <code>orrrrr</code> excluded.</li>\n</ol>\n\n<p>Enjoy.</p>\n'}, {'answer_id': 27420699, 'author': 'Shashank Saxena', 'author_id': 2735410, 'author_profile': 'https://Stackoverflow.com/users/2735410', 'pm_score': 1, 'selected': False, 'text': '<p>This is how i did it:</p>\n\n<pre><code>$string = "I appreciate your service &amp; idea to provide the branded toys at a fair rent price. This is really a wonderful to watch the kid not just playing with variety of toys but learning faster compare to the other kids who are not using the BooksandBeyond service. We wish you all the best";\n\nprint_r(substr($string, 0, strpos(wordwrap($string, 250), "\\n")));\n</code></pre>\n'}, {'answer_id': 31030129, 'author': 'evandro777', 'author_id': 1671683, 'author_profile': 'https://Stackoverflow.com/users/1671683', 'pm_score': 0, 'selected': False, 'text': "<p>I create a function more similar to substr, and using the idea of @Dave.</p>\n\n<pre><code>function substr_full_word($str, $start, $end){\n    $pos_ini = ($start == 0) ? $start : stripos(substr($str, $start, $end), ' ') + $start;\n    if(strlen($str) &gt; $end){ $pos_end = strrpos(substr($str, 0, ($end + 1)), ' '); } // IF STRING SIZE IS LESSER THAN END\n    if(empty($pos_end)){ $pos_end = $end; } // FALLBACK\n    return substr($str, $pos_ini, $pos_end);\n}\n</code></pre>\n\n<p>Ps.: The full length cut may be less than substr.</p>\n"}, {'answer_id': 32227063, 'author': 'Abhijeet kumar sharma', 'author_id': 1101353, 'author_profile': 'https://Stackoverflow.com/users/1101353', 'pm_score': -1, 'selected': False, 'text': "<p>Here you can try this</p>\n\n<pre><code>substr( $str, 0, strpos($str, ' ', 200) ); \n</code></pre>\n"}, {'answer_id': 32340759, 'author': 'orrd', 'author_id': 1257764, 'author_profile': 'https://Stackoverflow.com/users/1257764', 'pm_score': 2, 'selected': False, 'text': '<p>It\'s surprising how tricky it is to find the perfect solution to this problem.  I haven\'t yet found an answer on this page that doesn\'t fail in at least some situations (especially if the string contains newlines or tabs, or if the word break is anything other than a space, or if the string has UTF-8 multibyte characters).</p>\n\n<p>Here is a simple solution that works in all cases.  There were similar answers here, but the "s" modifier is important if you want it to work with multi-line input, and the "u" modifier makes it correctly evaluate UTF-8 multibyte characters.</p>\n\n<pre><code>function wholeWordTruncate($s, $characterCount) \n{\n    if (preg_match("/^.{1,$characterCount}\\b/su", $s, $match)) return $match[0];\n    return $s;\n}\n</code></pre>\n\n<p>One possible edge case with this... if the string doesn\'t have any whitespace at all in the first $characterCount characters, it will return the entire string.  If you prefer it forces a break at $characterCount even if it isn\'t a word boundary, you can use this:</p>\n\n<pre><code>function wholeWordTruncate($s, $characterCount) \n{\n    if (preg_match("/^.{1,$characterCount}\\b/su", $s, $match)) return $match[0];\n    return mb_substr($return, 0, $characterCount);\n}\n</code></pre>\n\n<p>One last option, if you want to have it add ellipsis if it truncates the string... </p>\n\n<pre><code>function wholeWordTruncate($s, $characterCount, $addEllipsis = \' …\') \n{\n    $return = $s;\n    if (preg_match("/^.{1,$characterCount}\\b/su", $s, $match)) \n        $return = $match[0];\n    else\n        $return = mb_substr($return, 0, $characterCount);\n    if (strlen($s) &gt; strlen($return)) $return .= $addEllipsis;\n    return $return;\n}\n</code></pre>\n'}, {'answer_id': 35061022, 'author': 'jdorenbush', 'author_id': 2321998, 'author_profile': 'https://Stackoverflow.com/users/2321998', 'pm_score': 0, 'selected': False, 'text': '<p>Added IF/ELSEIF statements to the code from <a href="https://stackoverflow.com/a/4665347/2321998">Dave</a> and <a href="https://stackoverflow.com/questions/79960/how-to-truncate-a-string-in-php-to-the-word-closest-to-a-certain-number-of-chara/4665347#comment36125374_4665347">AmalMurali</a> for handling strings without spaces</p>\n\n<pre><code>if ((strpos($string, \' \') !== false) &amp;&amp; (strlen($string) &gt; 200)) { \n    $WidgetText = substr($string, 0, strrpos(substr($string, 0, 200), \' \')); \n} \nelseif (strlen($string) &gt; 200) {\n    $WidgetText = substr($string, 0, 200);\n}\n</code></pre>\n'}, {'answer_id': 49194868, 'author': 'Namida', 'author_id': 9467793, 'author_profile': 'https://Stackoverflow.com/users/9467793', 'pm_score': -1, 'selected': False, 'text': "<p>I believe this is the easiest way to do it:</p>\n\n<pre><code>$lines = explode('♦♣♠',wordwrap($string, $length, '♦♣♠'));\n$newstring = $lines[0] . ' &amp;bull; &amp;bull; &amp;bull;';\n</code></pre>\n\n<p>I'm using the special characters to split the text and cut it.</p>\n"}, {'answer_id': 50290843, 'author': 'Mat Barnett', 'author_id': 2098954, 'author_profile': 'https://Stackoverflow.com/users/2098954', 'pm_score': 0, 'selected': False, 'text': '<p>I find this works:</p>\n<pre><code>function abbreviate_string_to_whole_word($string, $max_length, $buffer) {\n    if (strlen($string) &gt; $max_length) {\n        $string_cropped = substr($string, 0, $max_length - $buffer);\n        $last_space = strrpos($string_cropped, &quot; &quot;);\n        if ($last_space &gt; 0) {\n            $string_cropped = substr($string_cropped, 0, $last_space);\n        }\n        $abbreviated_string = $string_cropped . &quot;&amp;nbsp;...&quot;;\n    }\n    else {\n        $abbreviated_string = $string;\n    }\n    return $abbreviated_string;\n}\n</code></pre>\n<p>The buffer allows you to adjust the length of the returned string.</p>\n'}, {'answer_id': 53894324, 'author': 'Mahbub Alam', 'author_id': 6659365, 'author_profile': 'https://Stackoverflow.com/users/6659365', 'pm_score': -1, 'selected': False, 'text': "<p>Use this: </p>\n\n<p>the following code will remove ','. If you have anyother character or sub-string, you may use that instead of ','</p>\n\n<pre><code>substr($string, 0, strrpos(substr($string, 0, $comparingLength), ','))\n</code></pre>\n\n<p>// if you have another string account for    </p>\n\n<pre><code>substr($string, 0, strrpos(substr($string, 0, $comparingLength-strlen($currentString)), ','))\n</code></pre>\n"}, {'answer_id': 61022066, 'author': 'Will B.', 'author_id': 1144627, 'author_profile': 'https://Stackoverflow.com/users/1144627', 'pm_score': 1, 'selected': False, 'text': '<p>While this is a rather old question, I figured I would provide an alternative, as it was not mentioned and valid for PHP 4.3+.</p>\n\n<p>You can use the <a href="https://www.php.net/manual/en/function.sprintf.php" rel="nofollow noreferrer"><code>sprintf</code></a> family of functions to truncate text, by using the  <code>%.ℕs</code> precision modifier.</p>\n\n<blockquote>\n  <p>A period <code>.</code> followed by an integer who\'s meaning depends on the\n  specifier:</p>\n  \n  <ul>\n  <li>For e, E, f and F specifiers: this is the number of digits to be    printed after the decimal point (by default, this is 6).  </li>\n  <li>For g and G    specifiers: this is the maximum number of significant digits to be    printed.</li>\n  <li><strong>For s specifier: it acts as a cutoff point, setting a maximum character limit to the string</strong></li>\n  </ul>\n</blockquote>\n\n<h2>Simple Truncation <a href="https://3v4l.org/QJDJU" rel="nofollow noreferrer">https://3v4l.org/QJDJU</a></h2>\n\n<pre class="lang-php prettyprint-override"><code>$string = \'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ\';\nvar_dump(sprintf(\'%.10s\', $string));\n</code></pre>\n\n<p><strong>Result</strong></p>\n\n<pre><code>string(10) "0123456789"\n</code></pre>\n\n<hr>\n\n<h2>Expanded Truncation <a href="https://3v4l.org/FCD21" rel="nofollow noreferrer">https://3v4l.org/FCD21</a></h2>\n\n<p>Since <code>sprintf</code> functions similarly to <code>substr</code> and will partially cut off words. The below approach will ensure words are not cutoff by using <code>strpos(wordwrap(..., \'[break]\'), \'[break]\')</code> with a special delimiter. This allows us to retrieve the position and ensure we do not match on standard sentence structures.</p>\n\n<p>Returning a string without partially cutting off words and that does not exceed the specified width, while preserving line-breaks if desired.</p>\n\n<pre class="lang-php prettyprint-override"><code>function truncate($string, $width, $on = \'[break]\') {\n    if (strlen($string) &gt; $width &amp;&amp; false !== ($p = strpos(wordwrap($string, $width, $on), $on))) {\n        $string = sprintf(\'%.\'. $p . \'s\', $string);\n    }\n    return $string;\n}\nvar_dump(truncate(\'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ\', 20));\n\nvar_dump(truncate("Lorem Ipsum is simply dummy text of the printing and typesetting industry.", 20));\n\nvar_dump(truncate("Lorem Ipsum\\nis simply dummy text of the printing and typesetting industry.", 20));\n</code></pre>\n\n<p><strong>Result</strong></p>\n\n<pre><code>/* \nstring(36) "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"  \nstring(14) "Lorem Ipsum is" \nstring(14) "Lorem Ipsum\nis" \n*/\n</code></pre>\n\n<p>Results using <code>wordwrap($string, $width)</code> or <code>strtok(wordwrap($string, $width), "\\n")</code></p>\n\n<pre><code>/*\nstring(14) "Lorem Ipsum is"\nstring(11) "Lorem Ipsum"\n*/\n</code></pre>\n'}, {'answer_id': 66559438, 'author': 'HOSSEIN B', 'author_id': 14182190, 'author_profile': 'https://Stackoverflow.com/users/14182190', 'pm_score': 2, 'selected': False, 'text': "<p>You can use this:</p>\n<pre><code>function word_shortener($text, $words=10, $sp='...'){\n\n  $all = explode(' ', $text);\n  $str = '';\n  $count = 1;\n\n  foreach($all as $key){\n    $str .= $key . ($count &gt;= $words ? '' : ' ');\n    $count++;\n    if($count &gt; $words){\n      break;\n    }\n  }\n\n  return $str . (count($all) &lt;= $words ? '' : $sp);\n\n}\n</code></pre>\n<p>Examples:</p>\n<pre><code>word_shortener(&quot;Hello world, this is a text&quot;, 3); // Hello world, this...\nword_shortener(&quot;Hello world, this is a text&quot;, 3, ''); // Hello world, this\nword_shortener(&quot;Hello world, this is a text&quot;, 3, '[read more]'); // Hello world, this[read more]\n</code></pre>\n<h1>Edit</h1>\n<p>How it's work:</p>\n<p><strong>1. Explode space from input text:</strong></p>\n<pre><code>$all = explode(' ', $text);\n</code></pre>\n<p>for example, if <code>$text</code> will be &quot;Hello world&quot; then <code>$all</code> is an array with exploded values:</p>\n<p><code>[&quot;Hello&quot;, &quot;world&quot;]</code></p>\n<p><strong>2. For each word:</strong></p>\n<p>Select each element in exploded text:</p>\n<pre><code>foreach($all as $key){...\n</code></pre>\n<p>Append current word(<code>$key</code>) to <code>$str</code> and space if it's the last word:</p>\n<pre><code>$str .= $key . ($count &gt;= $words ? '' : ' ');\n</code></pre>\n<p>Then add 1 to <code>$count</code> and check if it's greater than max limit(<code>$words</code>) break the loop:</p>\n<pre><code>if($count &gt; $words){\n   break;\n}\n</code></pre>\n<p>Then return <code>$str</code> and separator(<code>$sp</code>) only if the final text is less than input text:</p>\n<pre><code>return $str . (count($all) &lt;= $words ? '' : $sp);\n</code></pre>\n"}, {'answer_id': 69532106, 'author': 'JesusIniesta', 'author_id': 3198983, 'author_profile': 'https://Stackoverflow.com/users/3198983', 'pm_score': 0, 'selected': False, 'text': '<blockquote>\n<p>As far as I\'ve seen, all the solutions here are only valid for the case when the starting point is fixed.</p>\n<p>Allowing you to turn this:</p>\n<pre class="lang-php prettyprint-override"><code>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna liqua. Ut enim ad minim veniam.\n</code></pre>\n<p>Into this:</p>\n<pre class="lang-php prettyprint-override"><code>Lorem ipsum dolor sit amet, consectetur...\n</code></pre>\n<h3>What if you want to truncate words surrounding a specific set of keywords?</h3>\n</blockquote>\n<h1>Truncate the text surrounding a specific set of keywords.</h1>\n<p>The goal is to be able to convert this:</p>\n<pre class="lang-php prettyprint-override"><code>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna liqua. Ut enim ad minim veniam.\n</code></pre>\n<p>Into this:</p>\n<pre class="lang-php prettyprint-override"><code>...consectetur adipisicing elit, sed do eiusmod tempor...\n</code></pre>\n<p>Which is a very common situation when displaying search results, excerpts, etc. To achieve this we can use these two methods combined:</p>\n<pre class="lang-php prettyprint-override"><code>    /**\n     * Return the index of the $haystack matching $needle,\n     * or NULL if there is no match.\n     *\n     * This function is case-insensitive  \n     * \n     * @param string $needle\n     * @param array $haystack\n     * @return false|int\n     */\n    function regexFindInArray(string $needle, array $haystack): ?int\n    {\n        for ($i = 0; $i &lt; count($haystack); $i++) {\n            if (preg_match(\'/\' . preg_quote($needle) . \'/i\', $haystack[$i]) === 1) {\n                return $i;\n            }\n        }\n        return null;\n    }\n\n    /**\n     * If the keyword is not present, it returns the maximum number of full \n     * words that the max number of characters provided by $maxLength allow,\n     * starting from the left.\n     *\n     * If the keyword is present, it adds words to both sides of the keyword\n     * keeping a balanace between the length of the suffix and the prefix.\n     *\n     * @param string $text\n     * @param string $keyword\n     * @param int $maxLength\n     * @param string $ellipsis\n     * @return string\n     */\n    function truncateWordSurroundingsByLength(string $text, string $keyword, \n            int $maxLength, string $ellipsis): string\n    {\n        if (strlen($text) &lt; $maxLength) {\n            return $text;\n        }\n\n        $pattern = \'/\' . \'^(.*?)\\s\' .\n                   \'([^\\s]*\' . preg_quote($keyword) . \'[^\\s]*)\' .\n                   \'\\s(.*)$\' . \'/i\';\n        preg_match($pattern, $text, $matches);\n\n        // break everything into words except the matching keywords, \n        // which can contain spaces\n        if (count($matches) == 4) {\n            $words = preg_split(&quot;/\\s+/&quot;, $matches[1], -1, PREG_SPLIT_NO_EMPTY);\n            $words[] = $matches[2];\n            $words = array_merge($words, \n                              preg_split(&quot;/\\s+/&quot;, $matches[3], -1, PREG_SPLIT_NO_EMPTY));\n        } else {\n            $words = preg_split(&quot;/\\s+/&quot;, $text, -1, PREG_SPLIT_NO_EMPTY);\n        }\n\n        // find the index of the matching word\n        $firstMatchingWordIndex = regexFindInArray($keyword, $words) ?? 0;\n\n        $length = false;\n        $prefixLength = $suffixLength = 0;\n        $prefixIndex = $firstMatchingWordIndex - 1;\n        $suffixIndex = $firstMatchingWordIndex + 1;\n\n        // Initialize the text with the matching word\n        $text = $words[$firstMatchingWordIndex];\n\n        while (($prefixIndex &gt;= 0 or $suffixIndex &lt;= count($words))\n                and strlen($text) &lt; $maxLength and strlen($text) !== $length) {\n            $length = strlen($text);\n            if (isset($words[$prefixIndex])\n                and (strlen($text) + strlen($words[$prefixIndex]) &lt;= $maxLength)\n                and ($prefixLength &lt;= $suffixLength \n                     or strlen($text) + strlen($words[$suffixIndex]) &lt;= $maxLength)) {\n                $prefixLength += strlen($words[$prefixIndex]);\n                $text = $words[$prefixIndex] . \' \' . $text;\n                $prefixIndex--;\n            }\n            if (isset($words[$suffixIndex])\n                and (strlen($text) + strlen($words[$suffixIndex]) &lt;= $maxLength)\n                and ($suffixLength &lt;= $prefixLength \n                     or strlen($text) + strlen($words[$prefixIndex]) &lt;= $maxLength)) {\n                $suffixLength += strlen($words[$suffixIndex]);\n                $text = $text . \' \' . $words[$suffixIndex];\n                $suffixIndex++;\n            }\n        }\n\n        if ($prefixIndex &gt; 0) {\n            $text = $ellipsis . \' \' . $text;\n        }\n        if ($suffixIndex &lt; count($words)) {\n            $text = $text . \' \' . $ellipsis;\n        }\n\n        return $text;\n    }\n</code></pre>\n<p>Now you can do:</p>\n<pre class="lang-php prettyprint-override"><code>$text = \'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do\' .\n        \'iusmod tempor incididunt ut labore et dolore magna liqua. Ut enim\' .\n        \'ad minim veniam.\';\n\n$text = truncateWordSurroundingsByLength($text, \'elit\', 25, \'...\');\n\nvar_dump($text); // string(32) &quot;... adipisicing elit, sed do ...&quot;\n</code></pre>\n<p><a href="http://sandbox.onlinephpfunctions.com/code/4f8a17dba1b7937c00a496424731b17917b9af9e" rel="nofollow noreferrer">Run code</a>.</p>\n'}, {'answer_id': 70406161, 'author': 'younghallaji', 'author_id': 16925848, 'author_profile': 'https://Stackoverflow.com/users/16925848', 'pm_score': 0, 'selected': False, 'text': "<pre><code>function trunc($phrase, $max_words) {\n       $phrase_array = explode(' ',$phrase);\n       if(count($phrase_array) &gt; $max_words &amp;&amp; $max_words &gt; 0)\n          $phrase = implode(' ',array_slice($phrase_array, 0, $max_words)).'...';\n       return $phrase;\n    }\n</code></pre>\n"}]
['https://Stackoverflow.com/questions/79960', 'https://Stackoverflow.com', 'https://Stackoverflow.com/users/14956/']