It's going to need more than that: there might be several unterminated elements of different types, and you'll need to terminate them in the right order so that they'll be properly nested. In between the opening tag and the end of the text there might be properly terminated elements possibly of the same type or of different types.
There is javascript to do (some of) this used in these forums (if you turn on all the bells and whistles in the post composition page you'll be able to find the src of the relevant .js file). In PHP...?
It's not going to be a one liner.
To fix <b>...<b>...</b> it would become <b>...</b><b>...</b> and then I guess to <b>......</b>. So, um
@<b>( #something that must not contain <b>
(?:(?!<b>).)*
)<b>( # something that must not contain </b>
(?:(?!</b>).)*
)</b>@six
replaced with
<b>$1$2</b>
Similarly for <i> and <u> and other elements.
After that, what would be needed would be a loop that matches the last unmatched opening tag, and appends the corresponding closing tag on the end of the text. That loop would repeat until there are no more unmatched opening tags. The good thing is that after the previous step, there are no </b> tags between an unmatched <b> tag and the end of the text, so it can be found with
@<b>((?:(?!</b>).)*)$@si
and replaced by
<b>$1</b>
Ditto for <i>, <u>, etc.
Edit: Another approach: Read through the text from start to finish, pushing opening tags onto a stack, and popping them off when the closing tag appears. If a closing tag </foo> doesn't match the opening tag <bar> on top of the stack, then the tags in the post are not well-formed: insert </bar><foo> into the text immediately before </foo>, and continue. At the end of the text, any tags still open are listed on the stack, in (from the top down) the order they need to be closed.