Trimming unicode whitespace, control characters, and line breaks with regex
So I've toiled away at this for a while and although I am feeling a little out of my depth I'll submit my approach which employs what I do understand about preg_replace and regex.


preg_replace() allows mixed type pattern and replacement parameters, so one call can execute multiple patterns/replacements.
Speeding up regex can be achieved by minimizing capture groups and alternatives (pipes) as well as using character classes and negated characters classes where appropriate.


I've prioritized accuracy and endeavored to provide the most robust patterns considering all possible character sequences that may be submitted from the textarea.  Then I've attempted to write each pattern with greater efficiency (without impacting accuracy).  Finally, I've tried to reduce the overall length and convolution of the regex patterns.

I want to be clear that I don't have any experience with recursive patterns.  I don't know how/when to use them effectively and so none of my patterns employ them.

This is the sample input that I used which has whitespaces, newlines (single and greater than double), and control characters:

$text="\0\n\n\1\n\r\n\13\27a ab\r\n\t\t\r\r\ncà\1êß€\0\0abcbc   d\n\n\t\r\n\5   e\2\n\3\n\4\n";


This input string was designed to be as difficult as possible to best test my method.

$patt_repl=[
    '/^[\s\pC]+|[\s\pC]+$/u'=&gt;'',  // aka mb_trim() -- remove all leading and trailing whitespace and control characters. DO NOT ADD m FLAG TO PATTERN, THAT WILL DAMAGE THE STRING
    '/[\s\pC]*?(\R)[\s\pC]*?(\R)[\s\pC]*/u'=&gt;"\n\n",  // cleans 2 or more new lines (uninterupted by non-white space characters)
    '/(?!\R)[\h\pC]*\R[\h\pC]*(?!\R)/u'=&gt;"\n",    // cleans single new lines
    '/\n+(*SKIP)(*FAIL)|[\h\pC]+/u'=&gt;' '  // convert 1 or more non-newline white-spaces and control characters to single space
];

$text=htmlspecialchars(preg_replace(array_keys($patt_repl),$patt_repl,$text), ENT_HTML5, 'UTF-8');

echo "This is the post-replace/result string dump:\n";
var_dump($text);


Yes, my method uses 4 separate patterns rather than 3 like the OP.  This is for improved accuracy.  I believe the OP's method had some gaps in accuracy, so that is the justification for the additional pattern.

The output from the above method is:

This is the post-replace/result string dump:
string(28) "a ab

cà êß€ abcbc d

e"


...which is what I believe the output should be.


It trims all leading and trailing whitespace and control characters from the input string.
It reduces two or more newline characters (which may have leading, in-between,  trailing whitespace or control characters) to \n\n.
It trims all leading and trailing horizontal whitespace or control characters from all single newline characters.
It converts 1 or more consecutive horizontal whitespace or control characters to  (a single space).  This, during my development, required me to use the (*SKIP)(*FAIL) technique to disqualify newline characters becausse \h wasn't doing what I expected.  I have read on SO and elsewhere that there can be some quirky/untrustworthy behaviors when dealing with \h and \v while processing certain characters on certain systems -- but I do not possess the necessary wisdom to explain this.


For my own confidence, I wanted to have a way to confirm the existence/absense of whitespace and control characters in the string before and after the method.  I found a very helpful function in the top comment at the php manual's ord() page.

Here is a PHP demo that iterates the string and identifies each and every character to prove effectiveness.

Here are a few relevant regex links that I visited during my research:

https://www.regular-expressions.info/unicode.html
https://stackoverflow.com/questions/3230623/filter-all-types-of-whitespace-in-php
https://stackoverflow.com/questions/5471644/what-are-the-whitespaces-matched-by-s-in-php

On the matter of ReDoS and Catastrophic Backtracking, the threat is minimal (if not non-existent) because of my use of character classes and the structure of my alternatives.  In case you haven't already visited it, here is a good page to read if you want to investigate further.

Finally, I humbly ask that if anyone has a string that breaks my method, please provide it to me so that I can update my answer.  And if any of my assertions are incorrect, please correct me.