The bracket notation specifies allowed characters (called a character class). So [aba] is the exact same thing as [ab] or [ba].
In your specific case with [legal]{4,10}, which is egal to [aegl]{4,10}, "gage" and "eagel" are ok, while "age" fails due to its length and "bagel" would fail due to the character 'b' which is not present in the character class.
I'm by no means an expert on regexp so it's entirely possible that someone like Nrg Alpha might have a smart solution, but my gut feeling tells me it would be hard to create such a pattern and that it will be less efficient than actually counting the number of occurences of each character and remove the current array element as soon as you hit 2 for any given character.
For example, this pattern (found with google) would only accept a string containing A, B, C and D where there is no sequential repetition, such as AA, BB, CC or DD
$pattern = "(?i:([A-D])(?!\1)([A-D])(?!\1|\2)([A-D])(?!\1|\2|\3)([A-D]))"
And it should be possible to extended this pattern to ascertain that the third character doesn't match the first one either etc, by following the same logic. However, it quickly becomes combersom to deal with increasing string length.
However, if you first check that your character class matches like you did, you could then validate that no character is repeated by looping over the characters in the string once.
# like before
$letters = 'legal';
$pattern = "/(\b[$letters]{4,10}\b)/i";
$wordlist = array(
'bagel',
'eagel',
'age',
'gale',
'legal');
echo '<h4>All words</h4>';
echo '<pre>'.print_r($wordlist,1).'</pre>';
$matches = preg_grep($pattern, $wordlist);
echo '<br/><h4>Acceptable letters</h4>';
echo '<pre>'.print_r($matches,1).'</pre>';
# remove words with repeating characters
foreach ($matches as $k => $m) {
$count = array();
$len = strlen($m);
for ($i = 0; $i < $len; ++$i) {
if (!isset($count[$m[$i]])) {
$count[$m[$i]] = 1;
}
else {
++$count[$m[$i]];
}
# see comment below php code window
if ($count[$m[$i]] > 1) {
unset($matches[$k]);
break;
}
}
}
echo '<br/><h4>No repetition</h4>';
echo '<pre>'.print_r($matches,1).'</pre>';
But perhaps you actually want [legal] to allow for the occurence of two l characters? In that case you could always count the number of occurences of each letter in $letters, then change
if ($count[$m[$i]] > 1) {
to check against maximum number of allowed occurences rather than just "> 1".