That's because $1 isn't a real variable - it's a placeholder so that the PCRE engine knows where you want the replacement. In other words, it's only valid in the context of the preg_*() functions themselves.
Instead, you could do something like:
function check_db($title) {
$query = 'SELECT 1 FROM news2 WHERE title = \'' . mysql_real_escape_string($title) . '\' LIMIT 1';
$exec = mysql_query($query);
return mysql_num_rows($exec);
}
$pattern = '/(?<=^|\+ )(.*?)(?=$| \+)/e';
$replace = 'check_db("$1") ? \'<a href="punk_band?groupe=$1&id=">$1</a>\' : "$1"';
echo preg_replace($pattern, $replace, $stuff);
Adding the 'e' modifier to the pattern tells the PCRE engine that the replacement string should be evaluated as PHP code.