agent-j's pattern is on the right track, but I would do something slightly more restrictive:
/€\d+(:?[.,]\d{2})?|\d+(:?[.,]\d{2})?€/
The only difference is that the decimal part is limited to 2 places, if it exists. I don't think you want to allow something like 99,999€
, especially since that could mean "99 thousand, 999 euros" if written in the American style.
What I think you're trying to get at in your reference to the cleanest and most efficient way is that the above pattern seems awkward and redundant when you look at it. It's basically the \d+(:?[.,]\d{2})?
portion repeated twice, with the € symbol switching sides. This feels wrong, but it isn't. You can't really get around it without bringing in just as much complexity, if not more. Even if you try to get around it with fancy lookarounds, it's going to look something like this:
/^(?=.*€)€?\d+(:?[.,]\d{2})?((?<!€.*)€)?$/
Clearly not an improvement. Sometimes the most obvious solution is the best one, even if it makes you feel dirty.
Note: If you want to get really crazy with it, you can try a variation (caution: untested, and I haven't done much PHP in a while):
$inner = "(:?\d{1,3}(?:([.,])\d{3})*(?:(?!\1)[.,]\d{2})?|\d*(?:[.,]\d{2})?)";
Usage:
preg_match ( "/€" . $inner . "|" . $inner . "€/", $string1, $matches)
That should also accept things like 99,999.99; 999999,99; 9.999.999,99; .99; etc.