You can use this pattern:
$pattern = '~\[url(?|=[\'"]?([^]"\']+)[\'"]?]([^[]+)|](([^[]+)))\[/url]~';
$replacement = '<a href="$1">$2</a>';
$result = preg_replace($pattern, $replacement, $subject);
The idea is to preserve the groups numbers for each branch of the alternation using the branch reset feature. In this way, group 1 contains always the url and group 2 the link description. When there is no description, the url is used in place, that's why the url is twice enclosed in a capture group for the second branch.