Ровно эту задачу можно решить функцией wordwrap -
https://www.php.net/wordwrap
Но она не корректно работает с юникод строками
В итоге быстрое гугление предлагает такие полифилы
<?php
function mb_wordwrap($string, $width=75, $break="\n", $cut = false) {
if (!$cut) {
$regexp = '#^(?:[\x00-\x7F]|[\xC0-\xFF][\x80-\xBF]+){'.$width.',}\b#U';
} else {
$regexp = '#^(?:[\x00-\x7F]|[\xC0-\xFF][\x80-\xBF]+){'.$width.'}#';
}
$string_length = mb_strlen($string,'UTF-8');
$cut_length = ceil($string_length / $width);
$i = 1;
$return = '';
while ($i < $cut_length) {
preg_match($regexp, $string,$matches);
$new_string = $matches[0];
$return .= $new_string.$break;
$string = substr($string, strlen($new_string));
$i++;
}
return $return.$string;
}
<?php
function mb_wordwrap($str, $width = 74, $break = "\r\n", $cut = false)
{
return preg_replace(
'~(?P<str>.{' . $width . ',}?' . ($cut ? '(?(?!.+\s+)\s*|\s+)' : '\s+') . ')(?=\S+)~mus',
'$1' . $break,
$str
);
}